TransactionScope Class

In the real world any one you will find exceptional scenarios, exceptional issues where only a knowledge of how to useTransactionScope is not good enough. To resolve transactional issues like deadlocks, timeouts, etc., you must know each and every concept directly/indirectly related to a transaction.

Use of TransactionScope in a .NET application is very, very simple. Any one can use it by following these steps:

  1. Add a System.Transactions assembly reference to the project.
  2. Create a transactional scope/area with the help of the TransactionScope class starting with a usingstatement.
  3. Writing code which needs to have transactional support.
  4. Execute the TransactionScope.Complete method to commit and finish a transaction.

    Local transaction: A transaction where a series of data manipulation statements execute as a whole on a single data source/database. A transaction which works with multiple data sources is called a distributed transaction. If a transaction fails then the affected data sources will be rolled back.
    If it finds the transaction nature is distributed based on some predefined rules it has a fallback transaction to the MSDTC distributed transaction.

    Connection Transaction

    Transaction which is tied directly with a database connection (SqlConnection) is called Connection Transaction. SqlTransaction (IDbTransaction) is an example of a connection transaction. In .NET Framework 1.0/1.1 we useSqlTransaction.

    string connString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
    using (var conn = new SqlConnection(connString))
    {
        conn.Open();
        using (IDbTransaction tran = conn.BeginTransaction())
        {
            try
            {
                // transactional code...
                using (SqlCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "INSERT INTO Data(Code) VALUES('A-100');";
                    cmd.Transaction = tran as SqlTransaction;
                    cmd.ExecuteNonQuery();
                }
                tran.Commit();
            }
            catch(Exception ex)
            {
                tran.Rollback();
                throw;
            }
        }
    }
    
    

    Ambient Transaction

    A transaction which automatically identifies a code block that needs to support a transaction without explicitly mentioning any transaction related things. An ambient transaction is not tied just to a database, any transaction aware provider can be used. TransactionScope implements an ambient transaction.

    TranactionScope Default Properties

    It is very important to know about the default properties of the TransactionScope object. Why? Because many times we create and use this object without configuring anything.

    Three very important properties are:

    1. IsolationLevel
    2. Timeout
    3. TransactionScopeOptions

    We create and use TransactionScope as follows:

    using (var scope = new TransactionScope())
    {
        //transctional code…
        scope.Complete();
    } 
    
    

    Here the TransactionScope object is created with the default constructor. We did not define any value forIsolationLevelTimeout, and TransactionScopeOptions. So it gets default values for all three properties. So now we need to know what the default property values of these properties.

    Property Default Value Available Options
    IsolationLevel Serializable Serializable, Read Committed, Read Un Committed, Repeatable Read
    Timeout 1 Minute Maximum 10 Minutes
    TransactionScopeOption Required Required, Required New, Suppress
    1. Isolation Level: It defines the locking mechanism and policy to read data inside another transaction.
    2. Timeout: How much time object will wait for a transaction to be completed. Never confuse it with the SqlCommand Timeout property. SqlCommand Timeout defines how much time the  SqlCommand object will wait for a database operation (select/insert/update/delete) to be completed.
    3. TransactionScopeOption: It is an enumeration. There are three options available in this enumeration:
    No Option Description
    1 Required It is default value for TransactionScope. If any already exists any transaction then it will join with that transaciton otherwise create new one.
    2 RequiredNew When select this option a new transaction is always created. This transaction is independent with its outer transaction.
    3 Suppress When select this option, no transaction will be created. Even if it already

    Is it possible to override the default property values? 

    Yes, you can. Suppose you want the default value to be 30 seconds and the maximum timeout value to be 20 minutes. If that is the requirement then you can do it using your web config.

    <system.transactions>    
        <defaultSettings timeout="30"/>
        <machineSettings maxTimeout="1200"/>
    </system.transactions>
    For the machineSettings value, you need to update your machine.config in your server.
    <section name="machineSettings" type="System.Transactions.Configuration.MachineSettingsSection,
    System.Transactions,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089,
    Custom=null"allowdefinition="MachineOnly"allowexedefinition="MachineToApplication" />

    Transaction Isolation Level Selection

    You need to have a proper knowledge when you use isolation levels. The following table will give you a very basic idea so that you can understand the basics and select the appropiate isolation level for your transaction scope.

    Isolation Level Suggestion
    Serializable It locks data exclusively at the time of read/write operations. For that reason, many times it may create a deadlock, and as a result you may get a timeout exception. You can use this isolation level for a highly secured transactional application like a financial application.
    Repeatable Read Same as Serializable except allows panthom rows. May use in a financial application or a heavily transactional application but need to know where panthom row creational scenarios are not there.
    Read Committed Most of the applications you can use it. SQL Server default isolation level is this.
    Read Un-Committed Applications with these have no need to support concurrent transactions.

Requirement 1

Create a transaction in which isolation level will be read committed and transaction timeout will be 5 minutes.

Implementation: 

var option = new TransactionOptions();
option.IsolationLevel = IsolationLevel.ReadCommitted;
option.Timeout = TimeSpan.FromMinutes(5);
using (var scope = new TransactionScope(TransactionScopeOption.Required, option))
{
    ExcuteSQL("CREATE TABLE MyNewTable(Id int);");                                        
    scope.Complete();
}

First off, create TransactionOptions and set ReadCommitted and 5 minutes to its IsolationLevel andTimeout property, respectively.

Second, create a transactional block by creating a  TransactionScope object with its parameterized constructor. In this constructor you will pass a  TransactionOptions object which you created early and the TransactionScopeOption.Required value.

One important note, many times we are confused when using a DDL statement (Data Definition Language) in a transaction and a question arises, will it support DDL transaction? The answer is yes. You can use a DDL statement like create/alter/ drop statement in the transaction. You can even use a Truncate statement inside the transaction.

Requirement 2

We need to create a transaction where a database operation will be in my local database and another will be in a remote database.

Implementation:

using (var scope = new TransactionScope())
{
    UpdateLocalDatabase();
    UpdateRemoteDatabase();
    scope.Complete();
}

There is no difference between a local or remote/distributed transaction implementation code in  transactions. Previously I said that TransactionScope implements ambient type transaction. This means, it automatically marks code blocks that need to support a transaction, local or remote. But you may find an error when working with distributed transactions. The error message will be like:

The partner transaction manager has disabled its support for remote/network transaction.

If you find that type of exception, you need to configure security settings, both your local and remote servers, for MSDTC, and make sure services are running.

To find the MSDTC configuration interface, you will go to:

ControlPanel > AdministritiveTools >ComponentServices > DistributedTransactionCoordinator > LocalDTC 

Some options for the Security tab are described bellow:  

Property Name Description
Network DTC Access If not selected, MSDTC will not allow any remote transaction
Allow Remote Clients If it is checked, MSDTC will allow to coordinate remote clients for transaction.
Allow Remote Administration Allow remote computers to access and configure these settings.
Allow Inbound Allow computers to flow transaction to local computers. This option is needed where MSDTC is hosted for a resource manager like SQL Server.
Allow Outbound Allow computers to flow transaction to remote computers. It is needed for a client computer where transaction is initiated.
Mutual Authentication Local and Remote computers communicate with encrypted messages. They establish a secured connection with the Windows Domain Account for message communication.
Incoming Calling Authentication Required If mutual authentication cannot be established but the incoming caller is authenticated then communication will be allowed. It supports only Windows 2003/XP ServicePack-2.
No Authentication Required It allows any non-authenticated non-encrypted communication.
Enable XA Transaction Allows different operating systems to communicate with MSDTC with XA Starndard.
DTC Logon Account DTC Service running account. Default account is Network Service.

Distributed Transaction Performance

Distributed transactions are slower than local transactions. A two phase commit protocol is used for managing distributed transactions. A two phase commit protocol is nothing but an algorithm by which a distributed transaction is performed. There are three commit protocols that are mostly used:

  1. Auto Commit: Transaction is committed automatically if all SQL statements are executed successfully or rolled-back if any of them fails to execute.
  2. Two Phase Commit: Transaction waits before final commit for messages from all other parties involved in transaction. It locks resources before commit or rollback. For this reason it is called a blocking protocol. In terms of performance it is the reason it is much slower. It is a widely used protocol for managing distributed transactions.
  3. Three Phase Commit: Transaction is finally committed if all nodes are agreed. It is a non-blocking protocol. In terms of performance it is faster than the two phase commit protocol. This protocol is complicated and more expensive but avoids some drawbacks in the two phase commit protocol.

Requirement 3

I want to create a transaction inside another transaction.

Implementation:

string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
var option = new TransactionOptions
{
     IsolationLevel = IsolationLevel.ReadCommitted,
     Timeout = TimeSpan.FromSeconds(60)
};
using (var scopeOuter = new TransactionScope(TransactionScopeOption.Required, option))
{
    using (var conn = new SqlConnection(connectionString))
    {
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText="INSERT INTO Data(Code, FirstName)VALUES('A-100','Mr.A')";
            cmd.Connection.Open();
            cmd.ExecuteNonQuery();
        }
    }
    using (var scopeInner = new TransactionScope(TransactionScopeOption.Required, option))
    {
        using (var conn = new SqlConnection(connectionString))
        {
            using (SqlCommand cmd = conn.CreateCommand())
            {
                cmd.CommandText="INSERT INTO Data(Code, FirstName) VALUES('B-100','Mr.B')";
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();
            }
        }
        scopeInner.Complete();
    }
    scopeOuter.Complete();
}

No problems in creating a transaction inside anohter (nested) transaction. You should define the behaviour or the inner transaction. This behaviour is dependent on the value of TransactionScopeOption. If you select Requiredas TransactionScopeOption, it will join its outer transaction. That means if the outer transaction is committed then the inner transaction will commit if the outer transaction is rolled back, then the inner transcation will be rolled back. If you select the  RequiredNew value of TrasnactionScopeOption, a new transaction will be created and this transaction will independently be committed or rolled back. You must be clear about those concepts before working with nested transactions using TransactionScope.

Requirement 4

I  want to call rollback explicitly from a transaction.

Implementation:

using (var scope = new TransactionScope())
{
    //either 1 of following lines will use
    Transaction.Current.Rollback();
    scope.Dispose();
    //if you comment the following line transaction will
    //automatically be rolled back
    //scope.Complete();
}

If you do not call the TransactionScope.Complete() method then the transaction will automatically be rolled back. If you need to explicitly call rollback for some scenarios, then you have two options:

  1. Executing Transaction.Current.Rollback() will rollback the current transaction.
  2. Executing TransactionScope.Dispose() will also rollback the current transaction.

Just one thing: remember that if you explicitly call Transaction.Rollback() or TranactionScope.Dispose()then you should not call the TransactionScope.Complete() method. If you do so then you will get an ObjectDisposeException.

“Cannot access a disposed object. Object name ‘TransactionScope'”

Requirement 5

I want to create a file/folder dynamically inside a transaction scope. If my transaction is rolled back then I want that created file/folder to be removed automatically, like a database row.

Implementation:

string newDirectory = @"D:\TestDirectory";
string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (var scope = new TransactionScope())
{
    using (var conn = new SqlConnection(connectionString))
    {
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText = "Insert into data(Code) values ('A001');";
            cmd.Connection.Open();
            cmd.ExecuteNonQuery();
        }
    }
    Directory.CreateDirectory(newDirectory);
    File.Create(@"D:\NewFile.txt").Dispose();
    scope.Dispose();
}

TranactionScope is not limited for only databases. It will support other data sources like FileSystem, MSMQ, etc. But you need more work to support TranactionScope. First of all what I show in the above code block will not work. Why? Because that directory creation and file creation will not be marked for transaction by default. Then what do we need to do?

public interface IEnlistmentNotification
{       
	void Commit(Enlistment enlistment);       
	void InDoubt(Enlistment enlistment);      
	void Prepare(PreparingEnlistment preparingEnlistment);        
	void Rollback(Enlistment enlistment);
}

The System.Transactions namespace has an interface named IEnlistmentNotification. If I want my component/service to be transaction aware then I need to implement that interface. The following code will show a very simple and straightforward way to implement this:

public class DirectoryCreator : IEnlistmentNotification
{
    public string _directoryName; 
    private bool _isCommitSucceed = false;
    public DirectoryCreator(string directoryName)
    {
        _directoryName = directoryName;
        Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
    }
    public void Commit(Enlistment enlistment)
    {
        Directory.CreateDirectory(_directoryName);
        _isCommitSucceed = true;
        enlistment.Done();
    }
    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }
    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared();
    }
    public void Rollback(Enlistment enlistment)
    {
        if (_isCommitSucceed))
            Directory.Delete(_directoryName);
        enlistment.Done();
    }
}

The above class will create a directory (folder) and this component is transaction aware. We can use this class with any TranactionScope and if TranactionScope is committed the directory will be created, otherwise it will be deleted (if already created). I show here just the diretory creation, if you want you can create a class/component for file creation. Now, how to use this class in the transactions scope?

string newDirectory = @"D:\TestDirectory";
string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (var scope = new TransactionScope())
{
    using (var conn = new SqlConnection(connectionString))
    {
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText = "Insert into data(Code) values ('A001');";
            cmd.Connection.Open();
            cmd.ExecuteNonQuery();
        }
    }
    var creator = new DirectoryCreator(newDirectory);
    Transaction.Current.Rollback();
    //scope.Complete();
}

Now, it will work!

Full Article: http://www.codeproject.com/Articles/690136/All-About-TransactionScope

Advertisements
By Sriramjithendra Posted in C#.NET

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s