Enlisting Resources as Participants in a Transaction

Each resource participating in a transaction is managed by a resource manager, whose actions are coordinated by a transaction manager. The coordination is done through notifications given to subscribers who have enlisted in a transaction through the transaction manager.

This topic covers how a resource (or multiple resources) can be enlisted in a transaction, as well as the different types of enlistment. The Committing a Transaction in Single-Phase and Multi-Phase topic covers how transaction commitment can be coordinated among enlisted resources.

Enlisting Resources in a Transaction

In order for a resource to participate in a transaction, it must enlist in the transaction. The Transaction class defines a set of methods whose names begin with Enlist that provide this functionality. The different Enlist methods correspond to the different types of enlistment that a resource manager may have. Specifically, you use the EnlistVolatile methods for volatile resources, and the EnlistDurable method for durable resources. The durability (or conversely the volatility) of a resource manager refers to whether the resource manager supports failure recovery. If a resource manager supports failure recovery, it persists data to durable storage during Phase1 (prepare) such that if the resource manager goes down, it can re-enlist in the transaction upon recovery and perform the proper actions based on the notifications received from the TM. In general, volatile resource managers manage volatile resources such as an in-memory data structure (for example, an in-memory transacted-hashtable), and durable resource managers manage resources that have a more persistent backing store (for example, a database whose backing store is disk).

For simplicity, after deciding whether to use the EnlistDurable or EnlistVolatile method based on your resource's durability support, you should enlist your resource to participate in Two Phase Commit (2PC) by implementing the IEnlistmentNotification interface for your resource manager. For more information on 2PC, see Committing a Transaction in Single-Phase and Multi-Phase.

A single participant can enlist for more than one of these protocols by calling EnlistDurable and EnlistVolatile multiple times.

Durable Enlistment

The EnlistDurable methods are used to enlist a resource manager for participation in the transaction as a durable resource. It is expected that if a durable resource manager is brought down in the middle of a transaction, it can perform recovery once it is brought back online by reenlisting (using the Reenlist method) in all transactions in which it was a participant and did not complete phase 2, and call RecoveryComplete once it finishes recovery processing. For more information on recovery, see Performing Recovery.

The EnlistDurable methods all take a Guid object as their first parameter. The Guid is used by the transaction manager to associate a durable enlistment with a particular resource manager. As such, it is imperative that a resource manager consistently uses the same Guid to identify itself even across different resource managers upon restarting, otherwise the recovery can fail.

The second parameter of the EnlistDurable method is a reference to the object that the resource manager implements to receive transaction notifications. The overload you use informs the transaction manager whether your resource manager supports the Single Phase Commit (SPC) optimization. Most of the time you would implement the IEnlistmentNotification interface to take part in Two-Phase Commit (2PC). However, if you want to optimize the commit process, you can consider implementing the ISinglePhaseNotification interface for SPC. For more information on SPC, see Committing a Transaction in Single-Phase and Multi-Phase and Optimization using Single Phase Commit and Promotable Single Phase Notification.

The third parameter is an EnlistmentOptions enumeration, whose value can be either None or EnlistDuringPrepareRequired. If the value is set to EnlistDuringPrepareRequired, the enlistment may enlist additional resource managers upon receiving the Prepare notification from the transaction manager. However, you should be aware that this type of enlistment is not eligible for the Single Phase Commit optimization.

Volatile Enlistment

Participants managing volatile resources such as a cache should enlist using the EnlistVolatile methods. Such objects might not be able to obtain the outcome of a transaction or recover the state of any transaction they participate in after a system failure.

As stated previously, a resource manager would make a volatile enlistment if it manages an in-memory, volatile resource. One of the benefits of using EnlistVolatile is that it does not force an unnecessary escalation of the transaction. For more information on transaction escalation, see Transaction Management Escalation topic. Enlisting volatility implies both a difference in how the enlistment is handled by the transaction manager, as well as what is expected of the resource manager by the transaction manager. This is because a volatile resource manager does not perform recovery. The EnlistVolatile methods do not take a Guid parameter, because a volatile resource manager does not perform recovery and would not call the Reenlist method which needs a Guid.

As with durable enlistments, whichever overload method you use to enlist denotes to the transaction manager whether your resource manager supports the Single Phase Commit optimization. Since a volatile resource manager cannot perform recovery, no recovery information is written for a volatile enlistment during the Prepare phase. Therefore, calling the RecoveryInformation method results in an InvalidOperationException.

The following example shows how to enlist such an object as a participant in a transaction using the EnlistVolatile method.

static void Main(string[] args)
{
    try
    {
        using (TransactionScope scope = new TransactionScope())
        {

            //Create an enlistment object
            myEnlistmentClass myEnlistment = new myEnlistmentClass();

            //Enlist on the current transaction with the enlistment object
            Transaction.Current.EnlistVolatile(myEnlistment, EnlistmentOptions.None);

            //Perform transactional work here.

            //Call complete on the TransactionScope based on console input
                            ConsoleKeyInfo c;
            while(true)
                            {
                Console.Write("Complete the transaction scope? [Y|N] ");
                c = Console.ReadKey();
                Console.WriteLine();

                                    if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
                {
                    scope.Complete();
                    break;
                }
                else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
                {
                    break;
                }
            }
        }
    }
    catch (System.Transactions.TransactionException ex)
    {
        Console.WriteLine(ex);
    }
    catch
    {
        Console.WriteLine("Cannot complete transaction");
        throw;
    }
}

class myEnlistmentClass : IEnlistmentNotification
{
    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        Console.WriteLine("Prepare notification received");

        //Perform transactional work

        //If work finished correctly, reply prepared
        preparingEnlistment.Prepared();

        // otherwise, do a ForceRollback
        preparingEnlistment.ForceRollback();
    }

    public void Commit(Enlistment enlistment)
    {
        Console.WriteLine("Commit notification received");

        //Do any work necessary when commit notification is received

        //Declare done on the enlistment
        enlistment.Done();
    }

    public void Rollback(Enlistment enlistment)
    {
        Console.WriteLine("Rollback notification received");

        //Do any work necessary when rollback notification is received

        //Declare done on the enlistment
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {
        Console.WriteLine("In doubt notification received");

        //Do any work necessary when in doubt notification is received

        //Declare done on the enlistment
        enlistment.Done();
    }
}
    Public Shared Sub Main()
        Try
            Using scope As TransactionScope = New TransactionScope()

                'Create an enlistment object
                Dim myEnlistmentClass As New EnlistmentClass

                'Enlist on the current transaction with the enlistment object
                Transaction.Current.EnlistVolatile(myEnlistmentClass, EnlistmentOptions.None)

                'Perform transactional work here.

                'Call complete on the TransactionScope based on console input
                Dim c As ConsoleKeyInfo
                While (True)
                    Console.Write("Complete the transaction scope? [Y|N] ")
                    c = Console.ReadKey()
                    Console.WriteLine()
                    If (c.KeyChar = "Y") Or (c.KeyChar = "y") Then
                        scope.Complete()
                        Exit While
                    ElseIf ((c.KeyChar = "N") Or (c.KeyChar = "n")) Then
                        Exit While
                    End If
                End While
            End Using
        Catch ex As TransactionException
            Console.WriteLine(ex)
        Catch
            Console.WriteLine("Cannot complete transaction")
            Throw
        End Try
    End Sub
End Class

Public Class EnlistmentClass
    Implements IEnlistmentNotification

    Public Sub Prepare(ByVal myPreparingEnlistment As PreparingEnlistment) Implements System.Transactions.IEnlistmentNotification.Prepare
        Console.WriteLine("Prepare notification received")

        'Perform transactional work

        'If work finished correctly, reply with prepared
        myPreparingEnlistment.Prepared()
    End Sub

    Public Sub Commit(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Commit
        Console.WriteLine("Commit notification received")

        'Do any work necessary when commit notification is received

        'Declare done on the enlistment
        myEnlistment.Done()
    End Sub

    Public Sub Rollback(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Rollback
        Console.WriteLine("Rollback notification received")

        'Do any work necessary when rollback notification is received

        'Declare done on the enlistment
        myEnlistment.Done()
    End Sub

    Public Sub InDoubt(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.InDoubt
        Console.WriteLine("In doubt notification received")

        'Do any work necessary when in doubt notification is received

        'Declare done on the enlistment
        myEnlistment.Done()
    End Sub
End Class

Optimizing Performance

The Transaction class also provides the EnlistPromotableSinglePhase method to enlist a Promotable Single Phase Enlistment (PSPE). This allows a durable resource manager (RM) to host and "own" a transaction that can later be escalated to be managed by the MSDTC if necessary. For more information on this, see Optimization using Single Phase Commit and Promotable Single Phase Notification.

See also