在事务中将资源登记为参与者
参与事务的每个资源都由资源管理器进行管理,而后者的操作则由事务管理器进行协调。 这一协调通过通知来执行,这些通知会提供给已通过事务管理器在事务中登记的订户。
本主题介绍如何在事务中登记一个资源(或多个资源)以及不同的登记类型。 以单阶段和多阶段提交事务主题介绍如何在已登记的资源之间协调事务提交。
在事务中登记资源
资源若要参与事务,它必须在事务中进行登记。 Transaction 类定义了一组提供此功能的方法,这些方法的名称以“Enlist”开头。 不同的“Enlist”方法对应于资源管理器可能具有的不同登记类型。 具体来说,EnlistVolatile 方法用于登记可变资源,而 EnlistDurable 方法则用于登记持久资源。 资源管理器的持久性(反之为可变性)是指资源管理器是否支持故障恢复。 如果资源管理器支持故障恢复,则它会在第 1 阶段(准备阶段)将数据保存到持久存储区中;这样,一旦资源管理器出现故障,它就可在恢复时在事务中重新登记,并根据从 TM 接收到的通知执行适当的操作。 通常,可变资源管理器管理如内存中的数据结构之类的可变资源(如内存中的事务处理哈希表),而持久资源管理器则管理具有更持久的后备存储区的资源(例如,其后备存储区为磁盘的数据库)。
为了简单起见,在根据资源的持久性支持决定是使用 EnlistDurable 还是 EnlistVolatile 方法后,应为资源管理器实现 IEnlistmentNotification 接口,从而将资源登记为参与两阶段提交 (2PC)。 有关 2PC 的更多信息,请参见以单阶段和多阶段提交事务。
单个参与者可以多次调用 EnlistDurable 和 EnlistVolatile 来登记到其中的某一协议。
持久登记
EnlistDurable 方法用于登记要作为持久资源参与事务的资源管理器。 如果持久资源管理器在事务执行期间关闭,则它在重新联机后,应在它作为参与者且未完成第 2 阶段的所有事务中重新登记(使用 Reenlist 方法)来执行恢复,并在完成恢复处理后调用 RecoveryComplete。 有关恢复的更多信息,请参见执行恢复。
EnlistDurable 方法都采用 Guid 对象作为其第一个参数。 事务管理器使用 Guid 将持久登记与特定的资源管理器关联。 因此,资源管理器在重新启动后必须统一使用同一 Guid 来标识自身,即使跨不同的资源管理器时也是如此;否则恢复操作就可能失败。
EnlistDurable 方法的第二个参数是对资源管理器所实现的用于接收事务通知的对象的引用。 您所使用的重载会向事务管理器通知资源管理器是否支持单阶段提交 (SPC) 优化。 大多数情况下,应实现 IEnlistmentNotification 接口来参与两阶段提交 (2PC)。 但如果要优化提交过程,可考虑实现 ISinglePhaseNotification 接口来参与 SPC。 有关 SPC 的更多信息,请参见以单阶段和多阶段提交事务和使用单阶段提交和可提升的单阶段通知进行优化。
第三个参数是 EnlistmentOptions 枚举,该枚举的值可以为 None 或 EnlistDuringPrepareRequired。 如果将该值设置为 EnlistDuringPrepareRequired,则这种登记类型可在从事务管理器接收到准备通知时登记附加资源管理器。 但是,您应清楚这种登记类型不适合执行单阶段提交优化。
可变登记
管理可变资源(如缓存)的参与者应使用 EnlistVolatile 方法进行登记。 此类对象可能无法获取事务的结果,或者在系统出现故障后可能无法恢复它们所参与的任何事务的状态。
如前面所述,资源管理器在管理内存中的可变资源时应进行可变登记。 使用 EnlistVolatile 的优点之一就是不会强制执行不必要的事务升级。 有关事务升级的更多信息,请参见事务管理升级主题。 登记可变性意味着事务管理器处理登记的方式与事务管理器对资源管理器的预期行为之间存在差异。 这是因为可变资源管理器不执行恢复。 EnlistVolatile 方法不采用 Guid 参数,因为可变资源管理器不执行恢复,并且不会调用需要 Reenlist 的 Guid 方法。
与持久登记一样,无论使用哪种重载方法进行登记,都会向事务管理器指示资源管理器是否支持单阶段提交优化。 由于可变资源管理器不能执行恢复,因此在准备阶段不会为可变登记写入任何恢复信息。 因此,调用 RecoveryInformation 方法会导致 InvalidOperationException。
下面的示例演示如何使用 EnlistVolatile 方法在事务中将此类对象登记为参与者。
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
优化性能
Transaction 类还提供了 EnlistPromotableSinglePhase 方法来登记可提升的单阶段登记 (PSPE)。 这使持久资源管理器 (RM) 可承载和“拥有”以后可在需要时升级为由 MSDTC 进行管理的事务。 有关此内容的更多信息,请参见使用单阶段提交和可提升的单阶段通知进行优化。