Introduction
COM+ distributed transactions can combine several disparate data sources under one transaction with a two-stage commit. Code running on different machines or in different processes can also participate in a distributed transaction using a TIP (Transaction Internet Protocol) URL.
Notice
TIP, while still available in .NET 1.1 and .NET 2.0, is an aging technology. With the upcoming release of Windows Vista, Microsoft will be phasing out its support for TIP. If you are still interested in transactional web services, there is an alternative. Check out my new article on Vista's WS-AtomicTransaction here[^]. As more companies adopt the WS-AtomicTransaction standard, this will be the way to do distributed transactions across web services in the future.
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.
TIP is actually a well-documented, standardized protocol. It allows several applications to communicate information about a transaction. With a normal distributed transaction, the DTC (Distributed Transaction Coordinator) communicates with your data sources to control the two-stage commit. Other applications on the same machine could potentially enlist in that transaction as long as the transaction object is passed to them. But, an application running on a different machine needs something better. This is what TIP is for.
This article borrows a lot from my previous article. In that article, I used NHibernate and got it to enlist in a COM+ distributed transaction. This article also needs to do some data access as an example, so I decided to just copy the NHibernate stuff over here. You do not have to use NHibernate to use the technique described in this article.
Using the code
There are several things I do in my code that you should be aware of:
- There is a web services project contained in the code called ComPlusTipWebService. Be sure to mount this as a virtual directory with the same name in IIS before opening up the solution.
- 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 Web.Config for the web services project.
- 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.
To get transactions working from client side to server side, there are a number of changes that must be made to the computers running it. In order to transmit DTC transaction information, a TIP URL is used. TIP is enabled by default on Windows 2000 but disabled by default on Windows XP and Server 2003. Also, it looks like Windows XP has a service pack that disables the network DTC access in general. To turn this back on, use the EnableTip.reg registry file to fix the keys. What it's looking at is the following:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSDTC\Security
The following keys are set to 1:
- NetworkDtcAccess
- NetworkDtcAccessTip
- NetworkDtcAccessTransactions
After setting these keys, restart the DTC service by opening up a command prompt and typing the following commands:
net stop msdtc
net start msdtc
ITipTransaction interface
After starting a transaction, the ContextUtil
class gives you access to a Transaction
property. The value of this property is an object that describes the current COM+ transaction. What you can do is cast this object to a System.EnterpriseServices.ITransaction
interface to get a little information out of it. Usually, this is not necessary though, since the EnterpriseServices library is taking care of most of the work for you.
In addition to being able to cast the transaction object to ITransaction
, you can also cast it to another interface called ITipTransaction
. This interface does not exist in the System.EnterpriseServices
library though. Which means you have to create one yourself to match the COM interface. Here is the code:
using System;
using System.Runtime.InteropServices;
namespace Common
{
[ComImport]
[Guid("17CF72D0-BAC5-11d1-B1BF-00C04FC2F3EF")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITipTransaction
{
void Push([MarshalAs(UnmanagedType.LPStr)]
string i_pszRemoteTmUrl,
[MarshalAs(UnmanagedType.LPStr)] out string
o_ppszRemoteTxUrl);
void GetTransactionUrl([MarshalAs(UnmanagedType.LPStr)] out string
o_ppzLocalTxUrl);
}
}
The interface is pretty simple. The method we're interested in the most is GetTransactionUrl
since the TIP URL is really all that we need. With our interface created, all we have to do to get the TIP URL for a currently open transaction is this:
ITipTransaction trans = (ITipTransaction)ContextUtil.Transaction;
string tipUrl = "";
trans.GetTransactionUrl(out tipUrl);
return tipUrl;
That's it! Now that we have the TIP URL, we can pass that to other applications so that they can enlist in the distributed transaction. The process for enlisting is also just as simple. Let's say I have a ServicedComponent
that looks like this:
[Transaction(TransactionOption.Supported)]
public class OrmManager : ServicedComponent { ... }
This class talks to my data source. I can create an object of this type within the TIP transaction's context. To do this, I use the System.EnterpriseServices.BYOT
(Bring Your Own Transaction) class:
OrmManager om = null;
om = BYOT.CreateWithTipTransaction(tipUrl,
typeof(OrmManager)) as OrmManager;
How the code works
This code is actually an extension of my previous article. So, if there are some confusing aspects about how I use NHibernate or the test harness, please take a look at that article. Basically, I had a TransactionController
class in that code that allowed me to do Save, Delete, and GetAll on a particular NHibernate class. I only implemented the methods I needed to perform the tests. The NUnit test harness from the previous article ran two tests to make sure that the distributed transactions were working. This article's code is not too much different. The big difference being that the persistence is done behind a web-service. So it's completely out of the application domain of the test harness. I did this to drive home the point that using TIP in an SOA environment could have some very interesting results.
Summary
I do thoroughly enjoy transactional code. Getting the databases, message queues, mainframe, and all the third party software to communicate under the same transaction would be quite a thrill. Especially in the case of a service oriented architecture. However, I don't see many people using COM+ to do this. I imagine there are many reasons, the biggest being how slow it is to get DTC to coordinate everything. Instead, it seems that a lot of programmers will choose to write some kind of proprietary transaction handling or just forget it all together. I hope that my articles can hope to uncover just how powerful and easy COM+ really is.
History
- 1.0: 2006-02-08: Initial revision.