Compensating Resource Managers (CRMs)

A compensating resource manager (CRM) is a service provided by COM+ that enables you to include nontransactional objects in Microsoft Distributed Transaction Coordinator (DTC) transactions. Although CRMs do not provide the capabilities of a full resource manager, they do provide transactional atomicity (all-or-nothing behavior) and durability through the recovery log.

The following example has two parts, a client and a server. The example shows how to create a custom CRM by using a Worker class, a Compensator class, and a Clerk object, and demonstrates how to perform a commit and an abort.

Server

Imports System
Imports System.IO
Imports System.Reflection
Imports System.EnterpriseServices
Imports System.EnterpriseServices.CompensatingResourceManager

<assembly: ApplicationActivation(ActivationOption.Server)>
<assembly: ApplicationCrmEnabled>
<assembly: AssemblyKeyFile("crm.key")>

Namespace CrmServer
' Create a Worker class.
<Transaction> Public Class CRMWorker
      Inherits Servicedcomponent
            Public Sub CRMMethod(filename As String, bCommit As Boolean)
                  ' Create the clerk object.
                  Dim myclerk As Clerk=New Clerk(GetType(CRMCompensator), "CRMCompensator", CompensatorOptions.AllPhases)
                  myclerk.WriteLogRecord(filename)
                  myclerk.ForceLog()
                  If bCommit=true Then
                        ContextUtil.SetComplete()
                  Else
                        ContextUtil.SetAbort()
                  End If
            End Sub
      End Class

      ' Create a class derived from the Compensator class.
      Public Class CRMCompensator
      Inherits Compensator
            Dim bBeginPrepareCalled As Boolean = False
            Dim bPrepareRecordCalled As Boolean = False
            Dim bBeginCommitCalled As Boolean = False
            Dim bCommitRecordCalled As Boolean = False
            Dim bBeginAbortCalled As Boolean = False
            Dim bAbortRecordCalled As Boolean = False
            Dim _filename as String

            Public Overrides Sub BeginPrepare()
                  bBeginPrepareCalled = True
            End Sub

            Public Overrides Function PrepareRecord(rec As LogRecord) As Boolean
                  dim o as Object = rec.Record
                  _fileName = o.ToString()
                  bPrepareRecordCalled = True
                  Return False
            End Function

            Public Overrides Function EndPrepare() As Boolean
                  if not bBeginPrepareCalled then Return False   

                  if not bPrepareRecordCalled then Return False
                  if _fileName="" then Return False
                  ' This is a Prepare Phase success.
                  Return True
            End Function

            Public Overrides Sub BeginCommit(fRecovery As Boolean)
                  bBeginCommitCalled = True
            End Sub

            Public Overrides Function CommitRecord(rec As LogRecord) As Boolean
                  bCommitRecordCalled = True
                  Return True
            End Function

            Public Overrides Sub EndCommit()
                  if not bBeginCommitCalled then Return 
                  if not bCommitRecordCalled then Return 
                  if _fileName="" then Return 
                  ' This is a Commit Phase success.
            End Sub

            Public Overrides Sub BeginAbort(fRecovery As Boolean)
                  bBeginAbortCalled = True
            End Sub
            
            Public Overrides Function AbortRecord(rec As LogRecord) As Boolean
                  bAbortRecordCalled = True
                  dim o as Object = rec.Record
                  _fileName = o.ToString()
                  Return True
            End Function

            Public Overrides Sub EndAbort()
                  if not bBeginAbortCalled then Return 
                  if not bAbortRecordCalled then Return 
                  if _fileName="" then Return
                  ' This is an Abort Phase success.
            End Sub
      End Class
End Namespace
      
      
[C#]
using System;
using System.IO;
using System.Reflection;
using System.EnterpriseServices;
using System.EnterpriseServices.CompensatingResourceManager;

[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationCrmEnabled]
[assembly: AssemblyKeyFile("crm.key")]

namespace CrmServer 
{
      [Transaction]
      // Create a Worker class.
      public class CRMWorker:ServicedComponent
      {
            public void CRMMethod(string fileName, bool bCommit)
            {
                  // Create clerk object.
                  Clerk clerk = new Clerk(typeof(CRMCompensator), "CRMCompensator", CompensatorOptions.AllPhases);
                  clerk.WriteLogRecord(fileName);
                  clerk.ForceLog();
                  if (bCommit)
                        ContextUtil.SetComplete();
                  else
                        ContextUtil.SetAbort();
            }
      
      }
      // Create class derived from Compensator class.
      public class CRMCompensator:Compensator
      {
            bool bBeginPrepareCalled = false;
            bool bPrepareRecordCalled = false;
            bool bBeginCommitCalled = false;
            bool bCommitRecordCalled = false;
            bool bBeginAbortCalled = false;
            bool bAbortRecordCalled = false;
      
            String _fileName;
      
            public override void BeginPrepare()
            {
                  bBeginPrepareCalled = true;
            }
      
            public override bool PrepareRecord(LogRecord rec)
            {
                  Object o = rec.Record;
                  _fileName = o.ToString();
                  bPrepareRecordCalled = true;
                  return false;
            }
      
            public override bool EndPrepare()
            {
                  if (!bBeginPrepareCalled)
                  {return false;}   
                  if (!bPrepareRecordCalled)
                  {return false;}   
                  if (_fileName==null)
                  {return false;}
                  // This is a Prepare Phase success.
                  return true;
            }
      
            public override void BeginCommit(bool fRecovery)
            {
                  bBeginCommitCalled = true;
            }
      
            public override bool CommitRecord(LogRecord rec)
            {
                  bCommitRecordCalled = true;
                  return true;
            }
      
            public override void EndCommit()
            {
                  if (!bBeginCommitCalled)
                  {return;}   
                  if (!bCommitRecordCalled)
                  {return;}
                  if (_fileName==null)
                  {return;}
                  // This is a Commit Phase success.
            }
      
            public override void BeginAbort(bool fRecovery)
            {
                  bBeginAbortCalled = true;
            }
      
            public override bool AbortRecord(LogRecord rec)
            {
                  bAbortRecordCalled = true;
                  Object o = rec.Record;
                  _fileName = o.ToString();
                  return true;
            }
      
            public override void EndAbort()
            {
                  if (!bBeginAbortCalled)
                  {return;}   
                  if (!bAbortRecordCalled)
                  {return;}               
                  if (_fileName==null)
                  {return;}
                  // This is an Abort Phase success.
            }
      
      }

}

Client

Imports System
Imports System.IO
Imports System.EnterpriseServices
Imports CrmServer
Imports System.Runtime.InteropServices

Public Class CRM
      Public Shared Sub Main()                        
                  dim logfilename As String = "crm.log"
                  Console.WriteLine("Creating a managed CRM worker object...")
                  dim crmworker As CRMWorker = new CRMWorker()
                  Console.WriteLine("Demonstrating a worker commit...")
                  crmworker.CRMMethod(logfilename, True)
                  Console.WriteLine("Demonstrating a worker abort...")
                  crmworker.CRMMethod(logfilename, False)
                  Console.WriteLine("DONE!")
                  Return            
      End Sub
End Class
      
[C#]
using System;
using System.IO;
using System.EnterpriseServices;
using CrmServer;
using System.Runtime.InteropServices;

class CRM
{
      public static int Main()
      {           
            string logfilename = "crm.log";
            Console.WriteLine("Creating a managed CRM worker object...");
            CRMWorker crmworker = new CRMWorker();
            Console.WriteLine("Demonstrating a worker commit...");
            crmworker.CRMMethod(logfilename, true);
            Console.WriteLine("Demonstrating a worker abort...");
            crmworker.CRMMethod(logfilename, false);
            Console.WriteLine("DONE!");
            return 0;   
      }
} 

Makefile.bat

You can compile the server and client program as follows:

sn –k crm.key
vbc /t:library /r:System.EnterpriseServices.dll crm.vb
vbc /r:crm.dll /r:System.EnterpriseServices.dll crmclient.vb
[C#]
sn –k crm.key
csc /t:library /r:System.EnterpriseServices.dll crm.cs
csc /r:crm.dll crmclient.cs

See Also

Summary of Available COM+ Services | System.EnterpriseServices Namespace