Distributed transactions in unmanaged mode

IBM MQ .NET classes support unmanaged connections (client) using extended transaction client and COM+/MTS as the transaction coordinator, using either implicit or explicit transaction programming model. In the unmanaged mode, IBM MQ .NET classes delegate all its calls to C extended transaction client that manages the transaction processing on behalf of .NET.

The transaction processing is controlled by an external transaction manager, coordinating the global unit of work under the control of the API of the transaction manager. The MQBEGIN, MQCMIT, and MQBACK verbs are unavailable. IBM MQ .NET classes expose this support by way of its unmanaged transport mode (C client). See Configure XA-compliant transaction managers

MTS is evolved as a transaction processing (TP) system to provide the same features on Windows NT as available in CICSĀ®, Tuxedo, and on other platforms. When the MTS is installed, a separate service is added to Windows NT called the Microsoft Distributed Transaction Coordinator (MSDTC). The MSDTC coordinates the transactions that span separate data stores or resources. To work, it requires each data store to implement its own proprietary resource manager.

IBM MQ becomes compatible with MSDTC by implementing an interface (proprietary resource manager interface) where it manages to map DTC XA calls to IBM MQ(X/Open) calls. IBM MQ plays the role of a resource manager.

When a component such as COM+ requests access to an IBM MQ, the COM usually checks with the appropriate MTS context object if a transaction is required. If a transaction is required, the COM informs the DTC and automatically starts an integral IBM MQ transaction for this operation. Then the COM works with the data through the MQMTS software, putting and getting messages as required. The object instance obtained from the COM calls the SetComplete or SetAbort method after all the actions on the data are over. When the application issues SetComplete, the call signals the DTC that the application has completed the transaction and the DTC can go ahead with the two-phase commit process. The DTC then issues calls to MQMTS which in turn issues calls to IBM MQ to commit or roll back the transaction.


Writing an IBM MQ .NET application using unmanaged client

To run within the context of COM+, a .NET class must inherit from System .EnterpriseServices.ServicedComponent. The rules and recommendations to create assemblies that use serviced components are the following:

Note: The following steps are relevant only if you are using System.EnterpriseServices mode.

  • The class and method being started in COM+ must both be public (no internal classes, and no protected or static methods).
  • The class and method attributes: The TransactionOption attribute dictates the transaction level of the class, that is whether the transactions are disabled, supported, or required. The AutoComplete attribute on the ExecuteUOW() method instructs COM+ to commit the transaction if no unhandled exception is thrown.
  • Strong-naming an assembly: The assembly must be strong-named and registered in the Global Assembly Cache (GAC). The assembly is registered in COM+ explicitly or by lazy registration after it is registered in the GAC.
  • Registering an assembly in COM+: Prepare the assembly to be exposed to COM clients. Then create a type library by using the Assembly Registration tool, regasm.exe.
    regasm UnmanagedToManagedXa.dll
    
  • Register the assembly into GAC gacutil /i UnmanagedToManagedXa.dll.
  • Register the assembly in COM+ by using the .NET services installer tool, regsvcs.exe. See the type library created by regasm.exe:
    Regsvcs /appname:UnmanagedToManagedXa /tlb:UnmanagedToManagedXa.tlb  UnmanagedToManagedXa.dll
    
  • The assembly is deployed into the GAC, and later it is registered in COM+ by lazy registration. The .NET framework takes care of the registration after the code is run for the first time.

The example code flow using System.EnterpriseServices model and System.Transactions with COM+ are described in the following sections:

    Example code flow using System.EnterpriseServices model
    using System;
    using IBM.WMQ;
    using IBM.WMQ.Nmqi;
    using System.Transactions;
    using System.EnterpriseServices;
    
    namespace UnmanagedToManagedXa
    {
    
    [ComVisible(true)]    [System.EnterpriseServices.Transaction(System.EnterpriseServices.TransactionOption.Required)]
        public class MyXa : System.EnterpriseServices.ServicedComponent
        {
    
            public MQQueueManager QMGR = null;
            public MQQueueManager QMGR1 = null;
            public MQQueue QUEUE = null;
            public MQQueue QUEUE1 = null;
            public MQPutMessageOptions pmo = null;
            public MQMessage MSG = null;
    
            public MyXa()
            {
            }
    
            [System.EnterpriseServices.AutoComplete()]
            public void ExecuteUOW()
            {
                QMGR = new MQQueueManager("usemq");
    
                QUEUE = QMGR.AccessQueue("SYSTEM.DEFAULT.LOCAL.QUEUE", 
                                          MQC.MQOO_INPUT_SHARED + 
                                          MQC.MQOO_OUTPUT + 
                                          MQC.MQOO_BROWSE);
    
                pmo = new MQPutMessageOptions();
                pmo.Options = MQC.MQPMO_SYNCPOINT;
                MSG = new MQMessage();
                QUEUE.Put(MSG, pmo);
                QMGR.Disconnect();
            }
        }
    }
    
    
    public void RunNow()
    {
       MyXa xa = new MyXa();
       xa.ExecuteUOW();
    }
    

    Example code flow using System.Transactions for interactions with COM+
    [STAThread]
    public void ExecuteUOW()
    {
    Hashtable t1 = new Hashtable();
    t1.Add(MQC.CHANNEL_PROPERTY, "SYSTEM.DEF.SVRCONN");
    t1.Add(MQC.HOST_NAME_PROPERTY, "localhost");
    t1.Add(MQC.PORT_PROPERTY, 1414);
    t1.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_CLIENT);
    TransactionOptions opts = new TransactionOptions();
    
    using(TransactionScope scope = new  TransactionScope(TransactionScopeOption.RequiresNew, 
                                    opts,     EnterpriseServicesInteropOption.Full)
      {
    
            QMGR = new MQQueueManager("usemq", t1);
            QUEUE = QMGR.AccessQueue("SYSTEM.DEFAULT.LOCAL.QUEUE", 
                                              MQC.MQOO_INPUT_SHARED + 
                                              MQC.MQOO_OUTPUT + 
                                              MQC.MQOO_BROWSE);
    
             pmo = new MQPutMessageOptions();
             pmo.Options = MQC.MQPMO_SYNCPOINT;
             MSG = new MQMessage();
             QUEUE.Put(MSG, pmo);
             scope.Complete();
      }
     QMGR.Disconnect();
    }