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
- Anonymous
December 23, 2003
Does this work on XP also? Or just 2003 server? - Anonymous
December 23, 2003
I have tested this on XP workstation. Not sure about Home edition. You also need .NET v1.1 - Anonymous
December 23, 2003
What would be the benefit of not using enterprise services to handle this transaction? Thanks! - Anonymous
December 23, 2003
I believe it should work on Win2K as well even though i haven't tested it. - Anonymous
December 23, 2003
The comment has been removed - Anonymous
December 23, 2003
The comment has been removed - Anonymous
June 26, 2004
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?