In-Depth

Speed Transaction Processing in .NET 2.0

The second-generation .NET Framework provides excellent features that greatly simplify the transaction processing in .NET applications.

Technology Toolbox: C#, .NET Framework 2.0, SQL Server 2005

Version 1.x of the .NET Framework gave developers some capable tools for controlling transactions, but the .NET Framework 2.0 expands the original capabilities significantly. Version 2 builds on the foundation of .NET 1.x's transaction features, providing new classes and methods that not only provide better performance, but are also easy to use. I'll show you how to take advantage of some of these features, which will translate to better-performing transaction code in your applications. I'll also walk you through some of the different scenarios in which you can take advantage of the new transaction features.

Let's start by looking at System.Transactions. .NET Framework 1.x provides support for transactions mainly through the System.EnterpriseServices namespace and by the System.Data.IDbTransaction interface, implemented by the different data providers. If you've used these models, you'll agree that both have some disadvantages; the former forces inheritance from ServicedComponent, the latter offers support only for local transactions in a specific database, and together they don't provide a complete programming model.

Now, with the release of the .NET Framework 2.0, the limitations are gone and transactions are available through a new and simple-to-use programming model, offering an increasing number of possibilities that can make your work simpler, safer, and even faster. System.Transactions is an object model with a handful of classes, interfaces, and enumerations. The key classes in this namespace include CommittableTransaction, Transaction, TransactionScope, Transaction, and TransactionOptions.

Some of the important features offered by System.Transactions include an easy-to-use programming model, support for transactional code blocks using the new TransactionScope object, a super-fast lightweight transaction manager for transactions that involve a single database, and automatic promotion from lightweight to distributed transactions on an as-needed basis.

One of the important features of the System.Transactions namespace in the .NET Framework is the way transaction services are decomposed down to a simple common set of services. This reduces the complexity associated with using transactions in your .NET applications. Note that these transaction features are intended not only for ADO.NET programmers, but can also be used in conjunction with technologies such as Microsoft Message Queuing (MSMQ) to perform transactional operations.

You can create and utilize a transaction from a .NET application easily. Simply enclose a code block in a TransactionScope object. The TransactionScope also has a number of overloaded constructors that provide various settings from timeout to COM+ style transaction settings such as RequiresNew, Required, and so on. Here's a typical pattern for using the TransactionScope object:

using (TransactionScope scope = new 
	TransactionScope())
{
//Add the code block that needs 
//transactional services
scope.Complete();
}

There is no further exposure until the application needs to handle transactions in a distributed manner. It doesn't require a special type of class, custom attributes, or a specific run time (see Listing 1 for an example of TransactionScope in action). In Listing 1, the code that executes the insert SQL statement against the database is enclosed in a TransactionScope object's using block. Once the code has executed successfully, you invoke the Complete() method of the TransactionScope object, resulting in the transaction being committed to the database (see Figure 1, which shows the output produced by the page when viewed through a browser).

Use Promotable Transactions in SQL Server 2005
The System.Transactions namespace can make the management of transactions quick and easy without causing you to worry about the complexities associated with ServicedComponent-based implementations. One of the greatest features of the lightweight transaction in System.Transactions is that the .NET Framework, in conjunction with SQL Server 2005, can determine automatically if it needs to promote a lightweight transaction to a distributed transaction that involves Microsoft Distributed Transaction Coordinator (MS-DTC). The lightweight transactions are also a faster alternative to using the DTC for local transactions.

Now I'll demonstrate how a fast, lightweight transaction covering a single SQL connection can be easily and automatically promoted to a full-distributed transaction spanning multiple connections and other DTC participants such as MSMQ. This can all take place within a single transaction context (using TransactionScope), without inheriting from ServicedComponent.

I'll build on Listing 1 by adding the execution of the same SQL statement through another SqlConnection object within the same TransactionScope object's using block (see Listing A). The first connection is made to a SQL Server 2005 version of the Pubs database to execute an Insert statement against the Dept table. The second connection is also made to the same Pubs database, executing the same SQL statement. When the connection to the first database is opened, the transaction is created as a lightweight transaction. However, when you open the second SqlConnection, the transaction is now promoted to a distributed transaction.

Note that this promotion from a lightweight to a distributed transaction happens only with SQL Server 2005. Figure 2 shows the distributed transaction statistics captured through the MS-DTC when viewed through the Component Services Explorer. As you can see, the promotion of a lightweight transaction to a distributed transaction results in MS-DTC being utilized to complete the distributed transaction.

Configure Transaction Settings
You can also configure transaction settings with an object called TransactionOptions. Listing 1 utilized the TransactionScope object with the default constructor, so it used the default isolation level and timeout values. The default value for the transaction isolation level is Serializable, and it is 60 seconds for the timeout. However, there are times when you might want to override these values with your own value, so that you have a finer level of control over the settings. The TransactionScope object provides a number of overloaded constructors that allow you to supply these values (see Listing B).

You can set the isolation level and the transaction timeout period by creating an instance of the TransactionOptions object and setting its IsolationLevel and Timeout properties to appropriate values. Next, supply this as an argument to the constructor of the TransactionScope object. In Listing B, the constructor of the TransactionScope object also takes the TransactionScopeOption enumeration as an argument. The TransactionScopeOption enumeration can take in any of these values: Required, RequiresNew, Supported, and NotSupported.

Control Explicit Transactions
There are times where the default, implicit, automatic transaction capability of the TransactionScope object might not provide you with the finer level of control you need. In these cases, you might want to create a transaction manually and commit or roll back the transaction explicitly (see Listing 2 for the steps involved in creating an explicit transaction using the CommittableTransaction class). You need to associate the SqlConnection object with the CommittableTransaction object by invoking the EnlistTransaction() method of the SqlConnection object, passing in the CommittableTransaction object as an argument.

Now you can commit or roll back the transactions explicitly through the call to the Commit() or Rollback() methods of the CommittableTransaction object. As you can imagine, this manual approach is not recommended, and you might run the risk of not rolling back the transaction when different types of exceptions occur.

One of the invaluable features of the .NET Framework 2.0 is its support for transactions. System.Transactions provides a better-performing transaction-processing model that can greatly increase the performance of your .NET applications. Use the basics you've learned here to construct sophisticated transaction mechanisms that you can use for performing operations such as executing queries or stored procedures against a wide range of data sources (download the four C# transaction examples here).

About the Author

Thiru Thangarathinam works at Intel in Chandler, Ariz. He specializes in architecting, designing, and developing .NET-based distributed enterprise-class applications. He has coauthored several books on .NET-related technologies. He has also been a frequent contributor to leading technology-related publications. Reach him at thiru.thangarathinam@intel.com.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.