Welcome to MSDN Blogs Sign in | Join | Help

A couple of weeks ago at TechReady1 in downtown Seattle, Mike Clark gave a presentation on System.Transactions and I had the opportunity to help him out with the hands on lab.  During the lab some of the attendants wanted to know how to create a transacted resource.  The following is a snippet of a volatile transacted integer.  The transacted integer is volatile so it does not perform recovery and it does not deal with asynchronous calls.  This snippet is only meant to give a basic understanding of what an enlistment looks like and how it could be used.


namespace ResourceManager
{
   using System;
   using System.Collections;
   using System.Text;
   using System.Transactions;

   public class TxInt
   {
      private int livevalue;
      private Hashtable enlistmentTable = new Hashtable();

      public TxInt()
      {
      }
      public TxInt(int value)
      {
         livevalue = value;
      }

      public int val
      {
         get
         {
            return GetValue();
         }

         set
         {
            SetValue(value);
         }
      }

      private void SetValue(int value)
      {
         Transaction tx;
         TxTmpValue enlistment;

         // Get current transaction
         tx = Transaction.Current;

         // If none -- set the real value
         if (tx == null)
         {
            livevalue = value;
            return;
         }

         // Check if already enlisted for that transaction
         enlistment = (TxTmpValue)enlistmentTable[tx];
         if (enlistment == null)
         {
            // If not -- create an enlistment structure
            enlistment = new TxTmpValue(this, tx);    

            // Enlist with TM
            tx.EnlistVolatile(enlistment, EnlistmentOptions.None);  
            enlistmentTable.Add(tx, enlistment);
         }

         enlistment.val = value;
      }

      private int GetValue()
      {

         Transaction tx;
         TxTmpValue enlistment;

         // Get current transaction
         tx = Transaction.Current;

         // If none -- return live value
         if (tx == null)
             return livevalue;

         // Running under a transaction context
         enlistment = (TxTmpValue)enlistmentTable[tx];
         if (enlistment == null)
             return livevalue;

         return enlistment.val;
     }

     // Called when the transaction is committed
     internal void CommitTransaction(int val, Transaction tx)
     {
         // Set the live value
         livevalue = val;

         // Remove the state associated with the transaction
         enlistmentTable.Remove(tx);
     }

     // Called when the transaction is aborted
     internal void AbortTransaction(Transaction tx)
     {
         // Remove the state associated with the transaction
         enlistmentTable.Remove(tx);
     }
 }

 internal class TxTmpValue : IEnlistmentNotification
 {
     internal int val;  // Holds the temporary value

     TxInt parent;     //Pointer to the 'real' value
     Transaction tx;    //Transaction it is enlisted in

     public TxTmpValue(TxInt p, Transaction t)
     {
         parent = p;
         tx = t;
     }

     void IEnlistmentNotification.Commit(Enlistment enlistment)
     {
         parent.CommitTransaction(val, tx);
         enlistment.Done();
     }

     void IEnlistmentNotification.InDoubt(Enlistment enlistment)
     {
         enlistment.Done();
     }

     void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
     {
         preparingEnlistment.Prepared();
     }

     void IEnlistmentNotification.Rollback(Enlistment enlistment)
     {
         parent.AbortTransaction(tx);
         enlistment.Done();
     }
  }
}
   

The TxInt class is a transacted integer.  We are defining two constructors and a property called val that you can Get and Set.

The livevalue integer represents the current value for the integer when it is accessed outside of a Transaction.  The enlistmentTable is a standard hash table that will hold the integer values that are Set under Transactions.

TxTempValue is a class that derives from IEnlistmentNotification that can enlist in a transaction.  This class enlists in the transaction and stores the temporary value of the integer for a given transaction.  So when a user tries to set the value of a TxInt object within a transaction, a TxTmpValue object will be created that stores the value of the integer temporarily until the transaction completes.

IEnlistmentNotification contains four methods (Prepare, Commit, Rollback, and InDoubt).  When you enlist in a Transaction, you pass in an object that implements this interface.  When Commit() is called on the CommittableTransaction, all enlistments will be asked to prepare.  This is done by the TransactionManager calling Prepare on all of the enlistments. When an enlistment is done preparing, it calls back Prepared() or ForceRollback().  Since our example does not do any work in Prepare, we always return Prepared.  Once the Transaction has completed, it will notify the enlistments by calling either Commit, Rollback or InDoubt, depending on the outcome of the Transaction.  In our example, we call AbortTransaction or CommitTransaction methods on the TxInt depending on the Transaction outcome.

SetValue() takes an integer and sets it as the current transacted integer value.  If SetValue is not called under a Transaction, it simply sets livevalue to the value passed in where it becomes the immediate value of the transacted integer.  If there is a Transaction in Transaction.Current, it will associate the value passed in with an enlistment, so that when the Transaction is Committed or Aborted, the enlistment can update livevalue appropriately.  SetValue checks the enlistment hash table to see if it has already created an enlistment for this Transaction.  If it has, it resets enlistment.val to the new value passed in.  If not, it will create a new enlistment object (TxTmpValue), add it to the enlistment table, and then enlist in the transaction with this new enlistment object.

GetValue() returns the current value of the transacted integer.  If there is no ambient transaction when Get is called, it returns the livevalue.  Otherwise, it checks to see if there are any enlistments associated with the Transaction.  If there is an enlistment, it returns the value associated with the enlistment because that is the current value until the Transaction commits or aborts

CommitTransaction takes the value that was stored by the enlistment and sets it as the livevalue.  It then removes the enlistment from the hash table because the Transaction is completed and will not be used again.

Performance versus ADO.Net:

I was asked to speak on the performance of System.Transactions versus ADO.Net.  Here is what I found out from one of our developers, Mike Clark:

“ADO.Net transactions continue to exist in Visual Studio 2005.  Our performance measurements indicate that using PSPE with SQL 2005 that using System.Transactions you can get 94% of the performance of using Ado.Net transactions.

If you are using SQL2000 rather than SQL 2005 then the performance you get will depend slightly on the configuration.  If you configure MSDTCPRX to use remote proxy to the same TM as your database then you should be able to get System.Transactions to perform at 56% of the performance of ADO.Net transactions directly."

 

Connecting to multiple SQL servers:

One of the System.Transactions performance gains is that it only escalates its transactions to distributed transactions if it is required.  This means that it doesn’t matter if you are working with one volatile resource or multiple durable resources on different machines, the application code will look the same.  System.Transactions will automatically escalate the transaction to use MSDTC under the covers, without any new tweaks or modifications to your code or your MSDTC settings.

So an example of connecting to multiple SQL servers might look like this:

try
{
  using (TransactionScope scope = new TransactionScope())
  {
    try
    {
      using (SqlConnection conn = new SqlConnection(connString))
      {
        conn.Open();
        // Build query and execute
      }                             
    }
    catch (Exception e)
    { }
   
    try
    {
      using (SqlConnection conn2 = new SqlConnection(connString2))
      {
        conn2.Open();
        // Build query and execute
      }                             
    }
    catch (Exception e)
    { }
    scope.Complete();
  }
}
catch (TransactionException e)
{ }

 

How Escalation is Initiated:

Transaction escalation is done for you automatically when you use System.Transactions and it is not something that you need to worry about.  However, the escalation reduces performance because the MSDTC resides in a separate process, and escalating a transaction to the MSDTC results in messages being sent across process. To improve performance, you should delay or avoid escalation to MSDTC; thus, you need to know how and when the escalation is initiated.

As long as the System.Transactions infrastructure handles volatile resources and at most one durable resource that supports single-phase notifications, the transaction remains in the ownership of the System.Transactions infrastructure. The transaction manager avails itself only to those resources that live in the same application domain and for which logging (writing the transaction outcome to disk) is not required. An escalation that results in the System.Transactions infrastructure transferring the ownership of the transaction to MSDTC happens when: 

-         At least one durable resource that does not support single-phase notifications is enlisted in the transaction.

-         At least two durable resources that support single-phase notifications are enlisted in the transaction. For example, enlisting a single connection with SQL Server 2005 does not cause a transaction to be promoted. However, whenever you open a second connection to a SQL Server 2005 database causing the database to enlist, the System.Transactions infrastructure detects that it is the second durable resource in the transaction, and escalates it to an MSDTC transaction.  

-         A request to "marshal" the transaction to a different application domain or different process is invoked. For example, the serialization of the transaction object across an application domain boundary. The transaction object is marshaled-by-value, meaning that any attempt to pass it across an application domain boundary (even in the same process) results in serialization of the transaction object. You can pass the transaction objects by making a call on a remote method that takes a Transaction as a parameter or you can try to access a remote transactional-serviced component. This serializes the transaction object and results in an escalation, as when a transaction is serialized across an application domain. It is being distributed and the local transaction manager is no longer adequate.

I recieved a request for some info on rolling back transactions and a litle more basic information.  I hope this helps...if not, let me know and I will add more:)

Committing and Rolling Back Transactions:

After Transactions are created, they can either committed or rolled back.  The process is the same for database and not-database related routines.  There is no concept of save points, so if the transaction rolls back, it will always be completely rolled back.

Implicit Programming Model for Transactions:

System.Transactions provides two programming models for creating Transactions; an implicit model and an explicit model.  With the implicit model, you create TransactionScopes and the TransactionScopes takes care of creating the transaction, setting the Transaction as the ambient transaction that will be used by transacted resource managers such as SQL, and committing/aborting the Transaction.  This implicit model is the recommended way to use Transactions as it is easy to use and requires less work on the part of the developer.  When you instantiate a new TransactionScope, the scope will determine if a new transaction needs to be created and will create one for you if necessary.  Then, at the end of the scope, you must call the Complete method on the TransactionScope to inform the transaction manager that it is acceptable to commit the transaction.  It is good practice to put the call to Complete as the last statement in the using block.  If you want to rollback the transaction, you should let the scope end without calling the Complete method.  One way to do this is to throw an exception inside the scope and the scope will automatically roll the transaction back for you. 

using (TransactionScope ts = new TransactionScope())
{
   // Do Database or Non-Database transacted work

    // If you create a connection to a SQL database here, SQL will automatically enlist in the TransactionScope’s transaction

     //Call complete on the TransactionScope or not based on input
    ConsoleKeyInfo c;
    while (true)
    {
       Console.Write("Complete the transaction scope? [Y|N] ");
        c = Console.ReadKey();
        Console.WriteLine();
        if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
        {
            // Commit the transaction
            ts.Complete();
            break;
        }
        else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
        {
            // This will rollback the transaction because you are exiting the scope without calling ts.Complete();
            break;
        }
    }
}

 

Explicit Programming Model for Transactions:

The CommittableTransaction class provides an explicit way for applications to use a transaction, as opposed to using the TransactionScope class implicitly. It is useful for applications that want to use the same transaction across multiple function calls or multiple thread calls. Unlike the TransactionScope class, the application writer needs to specifically call the Commit and Rollback methods in order to commit or abort the transaction.

You should note the followings when working with the CommittableTransaction class,

-         Creating a CommittableTransaction transaction does not set the ambient transaction. You need to specifically set and reset the ambient transaction, to ensure that resource managers operate under the right transaction context when appropriate. The way to set the current ambient transaction is by setting the static Current property on the global Transaction object.

-         A CommittableTransaction object cannot be reused. Once a CommittableTransaction object has been committed or rolled back, it cannot be used again in a transaction. That is, it cannot be set as the current ambient transaction context. 

 

//Create a committable transaction
CommittableTransaction tx = new CommittableTransaction();

// Do Database or Non-Database transactional work 

// If you create a database connection here, you will have to explicitly enlist in the connection using something like: myConnection.EnlistTransaction(tx);
// Commit or rollback the transaction
while (true)
{
    Console.Write("Commit or Rollback? [C|R] ");
    ConsoleKeyInfo c = Console.ReadKey();
    Console.WriteLine();
    if ((c.KeyChar == 'C') || (c.KeyChar == 'c'))
    {
        tx.Commit();
        break;
    }
    else if ((c.KeyChar == 'R') || (c.KeyChar == 'r'))
    {
        tx.Rollback();
        break;
    }
}
tx = null;

Hello,

My name is Nate Moch and I am a Software Design Engineer in Test at Microsoft and I work on the Transactions team.  Specifically, I have been working on the System.Transactions namespace that is part of Visual Studio 2005.  I can’t describe how excited I am about this new feature and the way it will change how you use Transactions!  System.Transactions makes it easier and faster than ever to incorporate Transactions into your applications.  I am blogging because I want to answer questions and spread the word on how to take advantage of all the cool features we have created!  If you would like to see some code snippets, have any questions, or would just like to share some of your opinions on System.Transactions, please post a comment, or send me an email at natemoch@microsoft.com.

For my first entry, here is a little introduction to System.Transactions.

Overview of System.Transactions

The System.Transactions infrastructure makes transactional programming simple and efficient by supporting SQL Server, ADO.NET, MSMQ, and the distributed transaction coordinator (DTC). It provides both an explicit programming model based on the Transaction class, as well as an implicit programming model using the TransactionScope class, in which transactions are automatically managed by the infrastructure.

For example, here is all you need to use Transactions with SQL:

try
{
  using (TransactionScope scope = new TransactionScope())
  {
    try
    {
      using (SqlConnection conn = new SqlConnection(connString))
      {
        conn.Open();
        // Build query and execute
      }                             
    }
    catch (Exception e)
    { }
    scope.Complete();
  }
}
catch (TransactionException e)
{ }

 

System.Transactions also provides types for you to implement a resource manager. The transaction manager native to the System.Transactions infrastructure allows volatile resources or a single durable resource enlistment to commit or roll back efficiently.

Here is the new interface used by resource managers to enlist in transactions:

tx.EnlistVolatile(new EnlistmentNotification, EnlistmentOptions.None);

public class EnlistmentNotification : IEnlistmentNotification
{
            Transaction tx = null;
            public EnlistmentNotification (Transaction transaction)
            {
                        this.tx = transaction;
            }
            public void Prepare(PreparingEnlistment preparingEnlistment)
            {
                        preparingEnlistment.Prepared();
            }
            public void Commit(Enlistment enlistment)
            {
                        enlistment.Done();
            }
            public void Rollback(Enlistment enlistment)
            {
                        enlistment.Done();
            }
            public void InDoubt(Enlistment enlistment)
            {
                        enlistment.Done();
            }
}

The transaction manager also transparently escalates local transactions to distributed transactions by coordinating through a disk-based transaction manager like the DTC, when an additional durable resource manager enlists itself with a transaction. There are two key ways that the System.Transactions infrastructure provides enhanced performance.

1.) Dynamic Escalation, which means that the System.Transactions infrastructure only engages the MSDTC when it is actually required for a transaction.

2.) Promotable Enlistments, which allows a resource, such as a database, to take ownership of the transaction if it is the only entity participating in the transaction. Later, if needed, the System.Transactions infrastructure can still escalate the management of the transaction to MSDTC. This further reduces the chance of using the MSDTC.

Please let me know if there is anything you would like to know regarding System.Transactions.

What is a dependent transaction?

 

A dependent transaction is a special clone of an ITransaction.  It guarantees that the transaction will not commit, even after Commit() is called on the root transaction, until the dependent transaction votes that it is ready. 

 

Dependent transactions are created by calling DependentClone on ICommittableTransactions or ITransactions.  After you create a dependent transaction, you can vote on whether the transaction should commit or not by calling Complete().  When you call Complete() on the dependent transaction, you are voting that you are ready for the transaction to commit. The Complete call can be called before or after Commit() is called on the root ICommittableTransaction.

 

There are two types of dependent transactions, the first of which will block (hold up) the commit process of the transaction until Complete() is called on the dependent transaction.  This means that even after Commit() is called on the root ICommittableTransaction, the transaction will not commit until Complete() is called.  The second type will abort the transaction if Commit() is called on the ICommittableTransaction before the dependent transaction has completed.

 

When do you use dependent transactions?


One place you may use blocking dependent transactions are in applications that queue off asynchronous work for a transaction and want the commit processing to wait until all of the work is complete.

 

A good example for non-blocking dependent transactions are applications that are doing synchronous work and want to make sure the transaction does not accidentally commit before the work is done.

 

 

How do you create dependent transactions?

 

IDependentTransaction is an interface that derives from ITransaction and implements one additional method called Complete().

 

To create an IDependentTransaction you call DependentClone on an ITransaction and pass in a Boolean called delayCommit.  If delayCommit is true, you will get a blocking dependent transaction that will hold up commit until you call Complete().  If delayCommit is false, a non-blocking transaction is returned that will roll the transaction back if Commit() is called before Complete().

 

 

Async Threading Example:

 

void Foo()

{

            // Create a transaction and then a new dependent clone for each worker thread

            ICommittableTransaction myTx = Transaction.Create();

 

            // Create a new dependent blocking clone and then pass it to each worker thread

            for(int i; i < numberOfThreads; i++)

            {

                IDependentTransaction depTx = myTx.DependentClone( true );

                ThreadPool.QueueUserWorkItem( new WaitCallback( AsyncWork ), depTx );

            }

 

            // At this point commit can be called on the transaction

            myTx.Commit();

}

 

 

void AsyncWork( object state )

{

    IDependentTransaction depTx = (IDependentTransaction) state;

 

   // Do the transactional work here.

   if (workWasSuccessful)

    {

        depTx.Complete();

    }

    Else

    {

        depTx.Rollback();

    }

}

 
Page view tracker