Meditation and Mindfulness for Software / IT Professionals. Conducted by Bipin Joshi in Thane. Read more...

Distributed Transactions in .NET 2.0

Introduction

Transactions are commonly used to update data as a batch. If any one operation from the batch is failed the entire batch of operations must be cancelled. Thus either all operations in the batch succeed or all of them are cancelled. ADO.NET 1.x provided SqlTransaction class that in conjunction with SqlConnection class provided to commit or rollback transactions. In .NET 2.0 an additional technique can be used and that is what is the topic of this article.

Types of Transactions

Transactions can be broadly classified as Single Transactions and Distributed Transactions. Single transactions involve only one database whereas distributed transactions involve multiple databases.

Transactional support provided by ADO.NET 1.x was single. Developers used COM+ features (System.ServiceModel namespace) to create distributed transactions. In .NET 2.0 you can work with distributed transactions without any direct interference with COM+.

The System.Transactions Namespace

.NET 2.0 provides System.Transactions namespace that provides classes for creating distributed transactions. The System.Transactions namespace resides in System.Transactions assembly. The TransactionScope class from System.Transactions namespace does the job of providing and completing a transaction. The TransactionScope works in such a way that you need not set any specific properties of SqlConnection or SqlCommand objects. It handles the job of creating, committing and rolling back a transaction in independent fashion. This way you can implement transactions on objects that are not originally written with transactional support. Note that the new architecture still depends in Microsoft Distributed Transaction Coordinator (MSDTC) for achieving the functionality.

Distributed Transactions - Good and Bad

Spanning a transaction across databases is not a simple job. The underlying resource manager (such as DTC) needs to do a lot of work internally. Typically resource managers use Two Phase Commit protocol to implement distributed transactions. The good part is that developers need not know every minute detail about this process. The new design simplifies that job considerably. However, distributed transactions come with their own price - performance penalty. Considering that a distributed transaction involves multiple database that are possibly heterogeneous there is bound to be performance overhead in the process. Luckily the new design takes care of this difficult as well. When you use TransactionScope class to create transactions by default they behave as a single transaction. The moment you open multiple connections the same single transaction gets promoted as a distributed transaction. That means if you use TransactionScope class for single transactions you won't get performance penalty. This way you can use consistent programming model for single as well as distributed transactions.

Example - Money Transfer

Let's understand the new distributed transaction model with the help of classic money transfer example. Assume that you want to transfer money from bank A to bank B. Thus money from one account must be withdrawn and deposited to another account. Both of these operations must be under one transaction so that either both of them succeed or both of them are cancelled. We demonstrate this scenario using a Windows Forms application.

First of create a new Windows Application using C# as the language.

Then right click on the project in the Solution Explorer and choose Add New Item menu option. Add a new SQL Server database called Database1.mdf in the project.

Open Server Explorer and create a new table called Bank in the newly added database. The schema of the table is shown below:

The Bank table consists of only two columns - AccountID (varchar(50), primary key) and Balance (money).

Now add a reference to System.Transactions asembly.

Also, add a new class called BankHelper and code it as shown below:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;

namespace DistTxnDemo
{
class BankHelper
{
private static string strConn;
private static int MinBalance = 500;

static BankHelper()
{
strConn = @"Data Source=.\SQLEXPRESS;
AttachDbFilename=C:\Bipin\DistTxnDemo\
Database1.mdf;Integrated Security=True;
User Instance=True";
}

public static bool ValidateBalance
(string accountno, decimal amount)
{
SqlConnection cnn = new SqlConnection(strConn);
cnn.Open();
SqlCommand cmd = new SqlCommand("select 
balance from BankA where accountid='" + accountno + 
"'", cnn);
object obj = cmd.ExecuteScalar();
decimal balance=Convert.ToDecimal(obj);
if (balance > MinBalance && (Convert.ToDecimal(obj) 
- amount) > MinBalance)
{
return true;
}
else
{
return false;
}
}

public static bool Transfer(string sourceAcc,
string destinationAcc,decimal amount)
{
try
{
if (ValidateBalance(sourceAcc, amount))
{
using (TransactionScope scope = 
new TransactionScope())
{
SqlConnection cnn1 = new SqlConnection(strConn);
SqlConnection cnn2 = new SqlConnection(strConn);
cnn1.Open();
cnn2.Open();

SqlCommand cmd1 = new SqlCommand
("update BankA set balance=balance-" + amount + 
" where accountid='" + sourceAcc + "'", cnn1);
SqlCommand cmd2 = new SqlCommand("update BankA set 
balance=balance+" + amount + " where accountid='" + 
destinationAcc + "'", cnn2);

cmd1.ExecuteNonQuery();
cmd2.ExecuteNonQuery();
cnn1.Close();
cnn2.Close();

scope.Complete();
return true;
}
}
else
{
return false;
}
}
catch
{
return false;
}
}
}
}

The BankHelper class consists of a single public method called Transfer. The Transfer() method accepts source account ID, destination account ID and amount to be transferred and returns true if transfer is successful. The helper method ValidateBalance() checks if the amount can be transferred by validating the available balance.

The ADO.NET code consisting of SqlConnection and SqlCommand objects is the same code that you would have used in ADO.NET 1.x. Notice the lines marked in bold letters. First we import System.Transactions namespace. Then we put a "using" block that creates an instance of TransactionScope class. All the database operations are enclosed within this "using" block. This way whenever we fire the first query a single transaction is created for us. When we fire the second query that uses the second SqlConnection the same single transaction now gets promoted as distributed one. If the code throws any exception the transaction is automatically rolled back. If the control reaches to the call of Complete() method the transaction is committed.

You might be wondering as to how to check if the transaction is really distributed. To prove that open Control Panel > Administrative Tools > Component Services and expand Distributed Transaction Coordinator folder. After you run the above application you should see some activity there (see below).

If your transaction contains only one SqlConnection object then you will not see any activity because it will participate in a single transaction.

That's it! You just developed an application that uses the new distributed transaction features of .NET 2.0.

 




Bipin Joshi is a software consultant, an author and a yoga mentor having 21+ years of experience in software development. He conducts online courses in ASP.NET MVC / Core, jQuery, and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced Yoga way of life he also teaches Meditation to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 08 Jul 2007



Tags : ADO.NET Data Access SQL Server .NET Framework