TransactionScope: A simple way to handle transactions in .NET

Have you ever tried implementing transactions using C# code? Normally, we implement transactions in SQL where multiple Insert/Update statements takes part in it. A Transaction follows the ACID (Atomicity, Consistency, Isolation, Durability) rule where either all the statements get committed or all get canceled and rolled back. TransactionScope allows us to implement it at application level. There could be some scenarios where you are required to do different operations in the same database or even multiple databases (distributed transaction) or due to some other constraints, it cannot be done at database level. It is also very helpful for application developers if they have less exposure to database.

What is TransactionScope

TransactionScope got introduced with .NET 2.0 as part of  System.Transaction. It is a class which provides a simple way to make a set of operations as part of a transaction without worrying about the complexity behind the scene. If any of the operation fails in between, entire transaction would fail and rolled back  which undo all the operation that got completed. All this would be taken care by the framework, ensuring the data consistency.

How to use the TransactionScope?

To use this, you need to add the reference of System.Transactions reference which is part of framework libraries (normally it wont be added by default). Once it get added, add the namespace System.Transactions wherever we want to use this. The syntax would look as

try
{
    using (TransactionScope scope = new TransactionScope())
    {
        // Do Operation 1
        // Do Operation 2
        //...

        // if all the coperations complete successfully, this would be called and commit the trabsaction. 
        // In case of an exception, it wont be called and transaction is rolled back
        scope.Complete();
    }
}
catch (ThreadAbortException ex)
{
    // Handle exception
}

Here we can see that we have used Disposable block while creating instance of TransactionScope, it makes sure the dispose gets called when it gets out of the block and ends the transaction scope.

In one Transaction scope, we can do multiple operation connecting to different databases as

using (TransactionScope scope = new TransactionScope())
{
    using (con = new SqlConnection(conString1))
    {
        con.Open();

        // Do Operation 1
        // Do Operation 2
        //...
    }

    using (con = new SqlConnection(conString2))
    {
        con.Open();

        // Do Operation 1
        // Do Operation 2
        //...

    }

    scope.Complete();
}

Here we are using two connection strings to connection different databases. We can use as many based on our requirement. We can have nested transactions as well. It could be as

public void DoMultipleTransaction()
{       
    try
    {
        using (TransactionScope scope = new TransactionScope())
        {
            using (con = new SqlConnection(conString1))
            {
                con.Open();
                // Do Operation 1
            }

            OtherTransaction();
            scope.Complete();
        }
    }
    catch (ThreadAbortException ex)
    {
        // Handle exception
    }
}

private void OtherTransaction()
{
    using (TransactionScope scope = new TransactionScope())
    {
        using (con = new SqlConnection(conString2))
        {
            con.Open();
            // Do Operations
        }
        scope.Complete();
    }
}

Here the outermost transaction is called as rootscope and here even if the inner transaction (OtherTransaction above) gets completed by calling scope.Complete(), if the rootscope complete could not be called due to various reasons, then the complete transaction would be rolled back including inner transactions.

Note: You might get one of the following exception while executing distributed trsanctions

  1. MSDTC on server is unavailable
  2. Network access for Distributed Transaction Manager (MSDTC) has been disabled.

 

Both the error are due to the same reason, first one occurs when you have the database and the application the same server while 2 if on the other server. For same server, go to run-> cmd-> services.msc. Run the service named Distributed Transaction Coordinator and make the startup type automatic so that it gets started again in case of system restart. For 2, follow the link to cofigure MSDTC.

TransactionScope provides various TransactionScopeOptions which defines transactions behavior for the scope. Lets see an example

using (TransactionScope scope = new TransactionScope())
{
    // Do Operation
    using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Required))
    {
        // Do Operation
        scope1.Complete();
    }
    using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        // Do Operation
        scope2.Complete();
    }
    using (TransactionScope scope3 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        // Do Operation
        scope3.Complete();
    }

    scope.Complete();
}

Here we created three transactions under the parent transaction with different TransactionScopeOptions. By default the scope is required, which applies to parent transaction here. It is a rootscope which creates a new transaction, and mark it as an ambient transaction. scope1 is also created with required and as we have already an ambient transaction (scope) so it joins the parent transaction. scope2 got created with option as RequiresNew which means it is a new transaction which is independently works with ambient transaction. scope3 got created with suppress option, which means it doesn’t take part in any ambient transactions. It gets executed regardless whether ambient transaction executes successfully or not. All the ambient transactions gets committed once the parent (global) scope completes.

Hope you enjoyed this post and will be using transaction in your future requirements.

Cheers,
Brij

Leave a comment