Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#
Article

Using NHibernate in COM+ (.NET Enterprise Services) Distributed Transactions

Rate me:
Please Sign up or sign in to vote.
4.20/5 (7 votes)
2 Feb 20065 min read 47.7K   534   29   2
Shows how to use NHibernate in a COM+ distributed transaction.

Introduction

COM+ distributed transactions and NHibernate are two very powerful tools available to the .NET programmer. This article shows how to get the two to cooperate in a simple way.

Background

Distributed transactions are my favorite part of using COM+. By opening up a distributed transaction, I can get a bunch of different databases, message queues, files, email, and almost anything else I can think of into the same transaction. The two-stage commit tells me that they all work or they all abort. And the transaction can be restarted from where it left off if a server goes down.

NHibernate is another powerful tool that I enjoy using. If you are not familiar with this ORM (object-relational mapper), then click here to visit the main site. NHibernate is built off of standard ADO.NET and also has capabilities to use transactions. But a regular ADO.NET transaction is for one database connection only. A COM+ distributed transaction can span multiple data sources. I wanted my NHibernate code to participate in a distributed transaction. This article shows how I accomplished this.

Using the code

There are several things I do in my code that you should be aware of:

  • The COM+ project has a post-build on it. The post-build uses regsvcs to unregister and re-register the COM+ DLL. This adds a little waiting time to compilation but saves me the trouble of having to do it by hand. To make regsvcs work, I added the framework directory to my path (C:\Windows\Microsoft.NET\Framework\v1.1.4322). As always, if you change your path variable, you have to restart Visual Studio.
  • I have created an NUnit test harness project as well to run the actual tests.
  • In the post-build for the test harness project, I copy the App.Config to the target directory and give it the DLL name with ".config" appended to it. This is so NUnit can pick it up.
  • Usually, I set up my test harness projects to be the startup projects. To make NUnit start the project, bring up the project properties window, select Configuration Properties->Debugging from the left side. Set the Debug Mode to Program, and hit Apply. For the start application, find the NUnit GUI executable (C:\Program Files\NUnit 2.2\bin\nunit-gui.exe). For the command line arguments, put the filename of the DLL (CPNTestHarness.dll).
  • I have included a SQL script to create the tables that I use in the example. Put these tables into your database and change the connection string located in the App.Config.
  • You may have noticed the weird way that I set up the NHibernate configuration. Instead of using the App.Config to set it up, I do it with code and pull the connection string from the App.Config. This is just a personal preference. I generally find that, in enterprise environments an App.Config just won't do the job, and usually pull my configuration settings from the Microsoft enterprise libraries. So I just quickly changed the code to work with this example.

OrmManager class

This class is basically how I handle talking to NHibernate. It creates the configuration and gets the ISessionFactory. It exposes only three methods for this example: Save(object), Delete(object), and GetAll(Type). These should all be pretty familiar to the average NHibernate user. I didn't include a specific fetch simply because I didn't need it to do the tests.

The real meat of the whole article centers around the EnlistIfPossible() method. When you are using regular ADO.NET, you can enlist your database connection in the COM+ distributed transaction by using the method EnlistDistributedTransaction. Unfortunately, this method is not part of any interface. It is also not part of the System.Data.IDbConnection class, which is basically all that NHibernate is going to give us. What we do know is that most of the implementations of IDbConnection have an EnlistDistributedTransaction method. One exception is the System.Data.SqlServerCe library.

So, to get our connections to participate in a distributed transaction, we simply have to call the EnlistDistributedTransaction method on the connection object, if it's there. To do this, I use reflection:

C#
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 });
      }
   }
}

Pretty simple stuff. Now, whenever we open up an NHibernate session, we can enlist the database connection into the COM+ transaction. This is done for Save and Delete.

C#
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();
   }
}

Note - I am by no means saying that it is OK to start a new session every time you do something in NHibernate. That's just plain slow. I did it in this example to illustrate the point that the connections are separate but still operate under the same distributed transaction. It's nice to not have to hold a transaction object yourself and just let COM+ handle it for you.

TransactionController class

The OrmManager will participate in a transaction if it exists. But it will not create a new transaction if there is not one. This is so that the user can pick whether to do transactions or not. To handle the transactions, there is a class that wraps around OrmManager, called TransactionController. This class basically allows me to start and end a transaction and do a whole bunch of ORM code in between.

Beginning and ending a transaction is a pretty standard chunk of COM+ code:

C#
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();
   ...
}

Test Harness

The NUnit test harness illustrates how the code can be used:

C#
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();
}

It's really that simple. COM+ and NHibernate free the developer from having to think about a lot of low-level programming. Of course, the real world isn't always this simple, but that's for another article.

History

  • 1.0 - Initial revision.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer Microsoft
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralNice article Pin
RonBurd7-Feb-06 2:41
RonBurd7-Feb-06 2:41 
AnswerRe: Nice article Pin
Dustin Metzgar7-Feb-06 4:56
Dustin Metzgar7-Feb-06 4:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.