Manually creating DTC Transactions


I have seen this article somewhere before. I am not able to find it. I modified the code little bit and reposting it here.


Scenario: There are cases where your application has to update your database and queue messages to MSMQ in a single atomic transaction scope. In .NETV1.0, the only way to achieve this is to use COM+ through System.EnterpriseServices. In .NET V1.1 there is another way to do this without using System.EnterpriseServices.


The idea here is to create a DTC transaction through TransactionDispenser component provided by DTC. Once you get the transaction object, you can pass the transaction object to your SQL provider as well as MSMQ APIs to participate in a single transaction. DTC takes care of two phase commit.


Sample code here explains how to obtain a transaction from DTC. Once you get the ITransaction interface, you can pass it on to any resource managers that support ITransaction. You have to use Interop to get the DTC Transaction.


using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.EnterpriseServices;


namespace Transactions
{


 [Flags]
 public enum ISOLATIONLEVEL
 {
  //ISOLATIONLEVEL_UNSPECIFIED      = 0xFFFFFFFF,
  ISOLATIONLEVEL_CHAOS            = 0x00000010,
  ISOLATIONLEVEL_READUNCOMMITTED  = 0x00000100,
  ISOLATIONLEVEL_BROWSE           = 0x00000100,
  ISOLATIONLEVEL_READCOMMITTED    = 0x00001000,
  ISOLATIONLEVEL_CURSORSTABILITY  = 0x00001000,
  ISOLATIONLEVEL_REPEATABLEREAD   = 0x00010000,
  ISOLATIONLEVEL_SERIALIZABLE     = 0x00100000,
  ISOLATIONLEVEL_ISOLATED         = 0x00100000
 }


 public enum ISOFLAG
 {
  ISOFLAG_RETAIN_COMMIT_DC  =  1,
  ISOFLAG_RETAIN_COMMIT     =  2,
  ISOFLAG_RETAIN_COMMIT_NO  =  3,
  ISOFLAG_RETAIN_ABORT_DC   =  4,
  ISOFLAG_RETAIN_ABORT      =  8,
  ISOFLAG_RETAIN_ABORT_NO   = 12,
  ISOFLAG_RETAIN_DONTCARE   =  7,
  ISOFLAG_RETAIN_BOTH       = 10,
  ISOFLAG_RETAIN_NONE       = 13,
  ISOFLAG_OPTIMISTIC        = 16,
  ISOFLAG_READONLY          = 32
 }



 //0x3A6AD9E1, 0x23B9, 0x11cf, 0xAD, 0x60, 0x00, 0xAA, 0x00, 0xA7, 0x4C, 0xCD);
 [Guid(“3A6AD9E1-23B9-11cf-AD60-00AA00A74CCD”)]
 [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
 public interface ITransactionDispenser
 {
  
  ushort GetOptionsObject([MarshalAs(UnmanagedType.IUnknown)]ref object options);


  int BeginTransaction(
   [MarshalAs(UnmanagedType.IUnknown)] object pUnknownOuter,
   ISOLATIONLEVEL isoLevel,
   ISOFLAG isoFlag,
   [MarshalAs(UnmanagedType.IUnknown)] object transactionOptions,
   [MarshalAs(UnmanagedType.Interface)] ref ITransaction pTransaction);
     
 }


 public class NativeSafeDTC
 {
  
  public static Guid DispenserIID = new Guid(“3A6AD9E1-23B9-11cf-AD60-00AA00A74CCD”);


  [DllImport(“xolehlp.dll”)]
  public static extern int DtcGetTransactionManager(
    IntPtr hostName,//[MarshalAsAttribute(UnmanagedType.LPStr)]string hostName,
    IntPtr tmName,//[MarshalAsAttribute(UnmanagedType.LPSTR)]string tmName,
    ref Guid iid,
    UInt32 dwReserved1,
    UInt16 wReserved2,
    IntPtr pvReserved,
    [MarshalAsAttribute(UnmanagedType.Interface)]ref ITransactionDispenser txnDispenser);
  
 }
}


Client Code


class Class1
 {
  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main(string[] args)
  {
   //
   // TODO: Add code to start application here
   //


   
   ITransactionDispenser oDispenser = null;


   int hResult = NativeSafeDTC.DtcGetTransactionManager(IntPtr.Zero,//null,
         IntPtr.Zero,//null,
         ref NativeSafeDTC.DispenserIID,
         0, 0, IntPtr.Zero,
         ref oDispenser);
   
   if (oDispenser != null)
   {
    ITransactionDispenser dispenser = oDispenser;


    Console.WriteLine(“Got a good dispenser”);


    
    for (int i=0; i < 100000; i++)
    {
     ITransaction transaction = null;
     dispenser.BeginTransaction(null, ISOLATIONLEVEL.ISOLATIONLEVEL_SERIALIZABLE,
      ISOFLAG.ISOFLAG_RETAIN_DONTCARE, null, ref transaction);
     if (transaction != null)
     {
      //Console.WriteLine(“Obtained good transaction object”);
      transaction.Commit(0,0,0);
     }
    }
   }
   


  }
 }

Comments (7)

  1. chadb says:

    Does this work on XP also? Or just 2003 server?

  2. ramkoth says:

    I have tested this on XP workstation. Not sure about Home edition. You also need .NET v1.1

  3. What would be the benefit of not using enterprise services to handle this transaction? Thanks!

  4. ramkoth says:

    I believe it should work on Win2K as well even though i haven’t tested it.

  5. ramkoth says:

    Paul:

    There are cases where you simply don’t want your components to be registered as COM+ components as things such as deployment etc will become little more involved. You may also want to consider perf implications of configuring component as COM+ (especially, if you run out of process).

    I am not saying that you don’t need COM+ for distributed transactions. But if your scenario mandates that your component should not be COM+ configured, then this approach would be helpful.

  6. Ahhhh I see now. Using your code example we can avoid the need to either manually/lazy register the component into COM+ and avoid that hassel. Thanks for the tip!

  7. Ralf says:

    What you left out in your example is enlisting a ADO.NET connection in the manually created DTC tx, e.g.

    SqlConnection conn = new SqlConnection("…");

    conn..Open();

    conn.EnlistDistributedTransaction(transaction);

    Even though this is easy, I´d prefer to get connections automatically enlisted in the/a running transaction, like it happens within a ServicedComponent. How can accomplish that?