65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (7投票s)

2006年2月2日

5分钟阅读

viewsIcon

48628

downloadIcon

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+ 事务中。这对于 SaveDelete 操作是这样做的。

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 - 初始修订。
© . All rights reserved.