Distributed Transactions in COM+ and .NET by doyleits

I was a little surprised, back when I started developing with the .NET Framework. Of course I was impressed with the framework, the amount of work and foresight that had gone into the System libraries, the ease-of-use and abundant documentation. After I was comfortable with programming in C# (VB6 applications were relegated to the “legacy code” arena), the inevitable question was asked, “What about transactions?”

My surprise came from the answer – “Use COM+.” OK, so we had all this new stuff, but we didn’t entirely rework the plumbing of distributed transactions? We’re still using COM+ and MSDTC? Yep.

Keep in mind, I was surprised, not disappointed. COM+ and the Distributed Transaction Coordinator work well. Sure, running code through COM+ can have some slight performance degradation, but what you gain outweighs that, and there are some security headaches with MSDTC in Windows XP SP2 and Windows Server 2003. But like they say, if it ain’t broke, don’t fix it.

Writing code to use transactions

In connected and disconnected systems, transactions are a vital asset to our daily operations. If we have momentary database downtime, or maintenance windows, or any other situation that might affect all-or-nothing processes, the ability to rollback the completed portions of an incomplete operation ensures the integrity of our data.

To execute within the context of COM+, a .NET class has to inherit from System.EnterpriseServices.ServicedComponent. There are some rules and recommendations when creating assemblies that use serviced components.

Public classes and methods

The class and method being invoked in COM+ must both be public (no internal classes, and no protected or static methods). The object is instantiated, a method is called, and some result or object can be passed back [see Figure 1].

Figure 1
[System.EnterpriseServices.Transaction(System.EnterpriseServices.TransactionOption.Required)] public class MyServicedComponent : System.EnterpriseServices.ServicedComponent
{
[System.EnterpriseServices.AutoComplete()] public void Execute()
{

}
}

You’ll notice the class and method attributes. The TransactionOption attribute dictates the transaction level of the class, whether transactions are disabled, supported, or required. The AutoComplete attribute on the Execute() method instructs COM+ to commit the transaction if no unhandled exception is thrown.

Stateful and stateless objects

Try to distinguish between stateful and stateless objects. For example, your Employee class instance should not reside in a COM+ context, but your DataHelper class should [see Figure 2]. Keep in mind that any class that exists in COM+ will not be as performant, so decide ahead of time exactly what you need to take advantage of distributed transactions. Updates are good candidates, while reads are not.

Figure 2
public class Employee
{
public string FirstName = “”;
public string LastName = “”;

public void Save()
{
DataHelper helper = new DataHelper();
helper.SaveEmployee(this);
}
}

[System.EnterpriseServices.Transaction(System.EnterpriseServices.TransactionOption.Required)] public class DataHelper : System.EnterpriseServices.ServicedComponent
{
[System.EnterpriseServices.AutoComplete()] public void SaveEmployee(Employee emp)
{
this.SaveToDatabase(emp, “database1”);
this.SaveToDatabase(emp, “database2”);
this.SendMessage(emp, “queue1”);
this.SendMessage(emp, “queue2”);
}

private void SaveToDatabase(Employee emp, string database)
{

}

private void SendMessage(Employee emp, string queue)
{

}
}

If at any point in the SaveEmployee() method an exception is thrown (and not caught), the transaction will be aborted, and all operations will be rolled back. This is true for database operations (for any database management system that supports MSDTC distributed transactions), as well as message queues!

Strong-named assemblies

The assembly must be strong-named, and should be registered in the Global Assembly Cache (GAC). Once registered in the GAC, the assembly can then be registered in COM+ explicitly or via lazy registration.

Strong-naming an assembly

If you do not have an enterprise or personal strong-name key (.snk) file, create it using the Strong Name tool, sn.exe. This is easily done using the Visual Studio .NET Command Prompt [see Figure 3].

Figure 3
sn -k MyKeyFile.snk

In your assembly, edit the AssemblyInfo class to point at the strong-name key file. Remember, it must be relative to your project output. If your key file is in the same directory as your project file, your AssemblyInfo entry should look like Figure 4.

Figure 4
[assembly: AssemblyKeyFile(@”….MyKeyFile.snk”)]

Registering an assembly in COM+

You must first prepare your assembly to be exposed to COM clients, and create a type library, by using the Assembly Registration tool, regasm.exe [see Figure 5], again using the Visual Studio .NET Command Prompt.

Figure 5
regasm MyServicedComponent.dll

This will create the necessary registry entries, as well as create a type library, which will be used shortly. Now we will register the assembly in the GAC, either by copying and pasting the assembly into C:Windowsassembly, or by using the GAC Utility, gacutil.exe [see Figure 6].

Figure 6
gacutil /i MyServicedComponent.dll

Now you can register your assembly in COM+ by using .NET Services Installer tool, regsvcs.exe [see Figure 7]. You will have to refer to your type library that was created by regasm.exe.

Figure 7
regsvcs /appname:MyServicedComponent /tlb:MyServicedComponent.tlb MyServicedComponent.dll

Alternatively to using regasm.exe, gacutil.exe, and regsvcs.exe, you can use a Setup And Deployment Project or Merge Module to deploy the assembly into the GAC. This is done using the Global Assembly Cache special folder. Once the assembly is deployed into the GAC, it will be registered in COM+ via lazy registration. This means that the first time code is executed, the .NET Framework will take care of the registration “under the covers.”

One caveat to this approach concerns security. The account executing the code for the first time must have sufficient privileges to make registry entries and modifications. Consult with your security or network administrators to resolve this issue; my experience has been that they prefer a relatively hands-free approach to deployment, and a merge module and MSI will offer this.

Distributed transactions in action

As mentioned earlier, distributed transactions can be extremely useful in disconnected and heterogenous environments, so the all-or-nothing approach when saving to multiple databases and dropping multiple MSMQ messages is desirable.

For example, I worked on a project where an ecommerce application had to save to its own database, then a customer database, then take the results of those operations, and fire off messages to a legacy mainframe application for point-of-sale. We couldn’t have missing or incomplete information in any system, so distributed transactions were enforced. In addition, we had to execute multiple stored procedures on each database, taking output parameter data from the first, and passing that data on in later stored procedures (and eventually the messages).

The issue we did encounter was SQL Server’s interaction with uncommited transactions. An identity column’s value generated after an INSERT statement (@@identity or scope_identity()) will not be returned unless you change the transaction isolation level in the stored procedure [see Figure 8].

Figure 8
set transaction isolation level read uncommitted

This information is not being presented as a best-practice, but rather as an option at your disposal. Other potential issues may be seen in clustered SQL Server environments with MSDTC, but the hassle and headache of resolving any issue is well worth the benefit of distributed transactions.

Final thoughts

As you’ve seen, the process of implementing a COM+-capable assembly or application is very simple, but yields an astounding benefit to your application and the enterprise. If applied with care and foresight, utilizing distributed transactions in COM+ will help lead to the reliablity of your .NET application!