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.