在 COM+ (.NET Enterprise Services) 分布式事务中使用 NHibernate






4.20/5 (7投票s)
2006年2月2日
5分钟阅读

48628

534
演示了如何在 COM+ 分布式事务中使用 NHibernate。
引言
COM+ 分布式事务和 NHibernate 是 .NET 程序员可用的两个非常强大的工具。本文展示了如何让这两个工具以一种简单的方式协同工作。
背景
分布式事务是我使用 COM+ 最喜欢的部分。通过开启一个分布式事务,我可以将许多不同的数据库、消息队列、文件、电子邮件以及我能想到的几乎任何东西纳入同一个事务中。两阶段提交告诉我它们要么全部成功,要么全部回滚。并且,如果服务器发生故障,事务可以从中断处重新开始。
NHibernate 是我喜欢使用的另一个强大工具。如果您不熟悉这个 ORM(对象关系映射器),请点击此处访问主站点。NHibernate 构建于标准的 ADO.NET 之上,并且也具备事务处理能力。但是,普通的 ADO.NET 事务仅适用于单个数据库连接。COM+ 分布式事务可以跨越多个数据源。我希望我的 NHibernate 代码能够参与分布式事务。本文展示了我如何实现这一点。
使用代码
我的代码中有几点您应该注意
- COM+ 项目有一个后期生成事件。后期生成事件使用 regsvcs 来注销并重新注册 COM+ DLL。这会增加编译时的一点等待时间,但可以省去我手动执行的麻烦。为了让 regsvcs 生效,我将 .NET Framework 目录添加到了我的路径中(C:\Windows\Microsoft.NET\Framework\v1.1.4322)。一如既往,如果您更改了路径变量,必须重启 Visual Studio。
- 我还创建了一个 NUnit 测试宿主项目来运行实际的测试。
- 在测试宿主项目的后期生成事件中,我将 App.Config 复制到目标目录,并将其命名为 DLL 名称加上“.config”后缀。这样 NUnit 才能找到它。
- 通常,我会将我的测试宿主项目设置为启动项目。要让 NUnit 启动项目,请打开项目属性窗口,从左侧选择“配置属性 -> 调试”。将“调试模式”设置为“程序”,然后单击“应用”。对于启动应用程序,找到 NUnit GUI 可执行文件(C:\Program Files\NUnit 2.2\bin\nunit-gui.exe)。对于命令行参数,请输入 DLL 的文件名(CPNTestHarness.dll)。
- 我包含了一个 SQL 脚本来创建示例中使用的表。将这些表放入您的数据库,并修改 App.Config 中的连接字符串。
- 您可能已经注意到我设置 NHibernate 配置的特殊方式。我没有使用 App.Config 来设置,而是通过代码进行设置,并从 App.Config 中读取连接字符串。这只是个人偏好。我通常发现,在企业环境中,App.Config 无法满足需求,并且通常会从 Microsoft 企业库中读取我的配置设置。因此,我只是快速修改了代码以适应这个示例。
OrmManager 类
这个类基本上是我处理与 NHibernate 通信的方式。它创建配置并获取 ISessionFactory
。对于这个示例,它只暴露了三个方法:Save(object)
、Delete(object)
和 GetAll(Type)
。这些对于一般的 NHibernate 用户来说应该都很熟悉。我没有包含特定的获取方法,仅仅是因为在进行测试时我不需要它。
整个文章的核心在于 EnlistIfPossible()
方法。当您使用普通的 ADO.NET 时,您可以通过使用 EnlistDistributedTransaction
方法将数据库连接注册到 COM+ 分布式事务中。不幸的是,这个方法不属于任何接口。它也不属于 System.Data.IDbConnection
类,而 NHibernate 基本上只会给我们这个。我们所知道的是,大多数 IDbConnection
的实现都有一个 EnlistDistributedTransaction
方法。一个例外是 System.Data.SqlServerCe
库。
因此,为了让我们的连接参与分布式事务,我们只需在连接对象上调用 EnlistDistributedTransaction
方法(如果存在)。为此,我使用了反射。
private static void EnlistIfPossible(System.Data.IDbConnection conn)
{
if (ContextUtil.IsInTransaction)
{
MethodInfo mi = conn.GetType().GetMethod("EnlistDistributedTransaction",
BindingFlags.Public | BindingFlags.Instance);
if (mi != null)
{
mi.Invoke(conn, new object[] {
(System.EnterpriseServices.ITransaction)
ContextUtil.Transaction });
}
}
}
非常简单。现在,每当我们打开一个 NHibernate 会话时,我们都可以将数据库连接注册到 COM+ 事务中。这对于 Save
和 Delete
操作是这样做的。
public void Save(object obj)
{
ISession session = SessionFactory.OpenSession();
try
{
EnlistIfPossible(session.Connection);
session.SaveOrUpdate(obj);
session.Flush();
if (ContextUtil.IsInTransaction)
ContextUtil.MyTransactionVote = TransactionVote.Commit;
}
catch
{
if (ContextUtil.IsInTransaction)
ContextUtil.MyTransactionVote = TransactionVote.Abort;
throw;
}
finally
{
if (session != null && session.IsOpen)
session.Close();
}
}
注意 - 我绝不认为每次在 NHibernate 中执行操作时都启动一个新会话是好的。那会非常慢。我在这里做只是为了说明连接是独立的,但仍然在同一个分布式事务下运行。不需要自己持有事务对象,而是让 COM+ 为您处理,这很好。
TransactionController 类
OrmManager
会在存在事务时参与其中,但如果不存在事务,它不会创建新的事务。这样用户就可以选择是否进行事务处理。为了处理事务,有一个类包装了 OrmManager
,称为 TransactionController
。这个类基本上允许我开始和结束一个事务,并在它们之间执行大量的 ORM 代码。
开始和结束事务是一段相当标准的 COM+ 代码。
public void BeginTransaction()
{
ServiceConfig sc = new ServiceConfig();
sc.Transaction = TransactionOption.RequiresNew;
ServiceDomain.Enter(sc);
ContextUtil.MyTransactionVote = TransactionVote.Commit;
...
}
public void EndTransaction()
{
if (ContextUtil.MyTransactionVote ==
TransactionVote.Commit)
ContextUtil.SetComplete();
else
ContextUtil.SetAbort();
ServiceDomain.Leave();
...
}
测试夹具
NUnit 测试宿主演示了代码如何使用。
TransactionController tc = new TransactionController();
Table1 t1; // My NHibernate object
Table2 t2; // My NHibernate object
try
{
tc.BeginTransaction();
t1 = new Table1();
t1.Num = 1;
t2 = new Table2();
t2.Num = 2;
tc.Save(t1);
tc.Save(t2);
}
finally
{
tc.EndTransaction();
}
就这么简单。COM+ 和 NHibernate 让开发人员无需考虑许多底层编程。当然,现实世界并不总是这么简单,但这属于另一篇文章的范畴。
历史
- 1.0 - 初始修订。