使用 CommittableTransaction 执行显式事务

CommittableTransaction 类为应用程序使用事务提供了一种显式方法,而不是隐式地使用 TransactionScope 类。 对于要跨多个函数调用或多个线程调用使用同一事务的应用程序,前一种类十分有用。 与 TransactionScope 类不同,应用程序编写器需要明确调用 CommitRollback 方法以提交或中止事务。

CommittableTransaction 类概述

CommittableTransaction 类是从 Transaction 类派生的,因此它提供了后者所具有的所有功能。 Rollback 类上的 Transaction 方法尤其有用,该方法还可用于回滚 CommittableTransaction 对象。

Transaction 类与 CommittableTransaction 类相似,但前者不提供 Commit 方法。 这使您可以将事务对象(或其克隆)传递给其他方法(可能位于其他线程上),同时仍可控制事务的提交时间。 被调用的代码可以在事务中登记并为其投票,但只有 CommittableTransaction 对象的创建者才能提交事务。

此外,在使用 CommittableTransaction 类时,还应注意以下几点。

  • 创建 CommittableTransaction 事务并不会设置环境事务。 您需要明确设置和重置环境事务,才能确保资源管理器在需要时在正确的事务上下文中操作。 设置当前环境事务的方式是在全局 Current 对象上设置静态 Transaction 属性。

  • CommittableTransaction 对象不能被重用。 CommittableTransaction 对象一旦提交或回滚后,就不能再在事务中使用。 也就是说,它不能设置为当前环境事务上下文。

创建可提交的事务

下面的示例创建一个新的 CommittableTransaction 并提交它。

//Create a committable transaction
tx = new CommittableTransaction();

SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
SqlCommand myCommand = new SqlCommand();

//Open the SQL connection
myConnection.Open();

//Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx);

myCommand.Connection = myConnection;

// Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
myCommand.ExecuteNonQuery();

// Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
myCommand.ExecuteNonQuery();

// Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
myCommand.ExecuteNonQuery();

// Commit or rollback the transaction
while (true)
{
    Console.Write("Commit or Rollback? [C|R] ");
    ConsoleKeyInfo c = Console.ReadKey();
    Console.WriteLine();

    if ((c.KeyChar == 'C') || (c.KeyChar == 'c'))
    {
        tx.Commit();
        break;
    }
    else if ((c.KeyChar == 'R') || (c.KeyChar == 'r'))
    {
        tx.Rollback();
        break;
    }
}
myConnection.Close();
tx = null;
tx = New CommittableTransaction

Dim myConnection As New SqlConnection("server=(local)\SQLExpress;Integrated Security=SSPI;database=northwind")
Dim myCommand As New SqlCommand()

'Open the SQL connection
myConnection.Open()

'Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx)

myCommand.Connection = myConnection

'Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"
myCommand.ExecuteNonQuery()

'Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"
myCommand.ExecuteNonQuery()

'Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"
myCommand.ExecuteNonQuery()

'Commit or rollback the transaction
Dim c As ConsoleKeyInfo
While (True)
    Console.Write("Commit or Rollback? [C|R] ")
    c = Console.ReadKey()
    Console.WriteLine()

    If (c.KeyChar = "C") Or (c.KeyChar = "c") Then
        tx.Commit()
        Exit While
    ElseIf ((c.KeyChar = "R") Or (c.KeyChar = "r")) Then
        tx.Rollback()
        Exit While
    End If
End While

myConnection.Close()
tx = Nothing

创建 CommittableTransaction 的实例并不会自动设置环境事务上下文。 因此,资源管理器上的任何操作都不属于该事务。 全局 Current 对象上的静态 Transaction 属性用于设置或检索环境事务,应用程序必须手动设置该属性,才能确保资源管理器可参与事务。 此外,还有一个很好的做法,就是保存旧环境事务并在完成时使用 CommittableTransaction 对象还原它。

若要提交事务,需要显式调用 Commit 方法。 若要回滚事务,应调用 Rollback 方法。 请注意,还有一点很重要,就是在提交或回滚 CommittableTransaction 之前,该事务中所涉及的所有资源仍处于锁定状态。

可跨函数调用和线程使用 CommittableTransaction 对象。 但是,在发生故障时,应由应用程序开发人员负责处理异常并明确调用 Rollback(Exception) 方法。

异步提交

CommittableTransaction 类还提供了一种以异步方式提交事务的机制。 事务提交可能会耗费大量时间,因为它可能涉及多个数据库访问和可能的网络延迟。 如果要在高吞吐量应用程序中避免死锁,可以使用异步提交尽快地完成事务工作,并将提交操作作为后台任务运行。 此操作可以通过 BeginCommit 类的 EndCommitCommittableTransaction 方法执行。

调用 BeginCommit 可将提交延迟调度到线程池中的某一线程。 此外,还可以调用 EndCommit 来确定是否确实已提交事务。 如果无法提交事务(无论出于什么原因),EndCommit 就会引发事务异常。 如果在调用 EndCommit 时仍未提交事务,则调用方就会处于锁定状态,直到提交或中止事务为止。

执行异步提交的最简单方法就是提供要在完成提交时调用的回调方法。 但是,必须在用于执行该调用的原始 EndCommit 对象上调用 CommittableTransaction 方法。 若要获取该对象,可以向下转换该回调方法的 IAsyncResult 参数,因为 CommittableTransaction 类实现 IAsyncResult 类。

下面的示例演示如何执行异步提交。

public void DoTransactionalWork()  
{  
     Transaction oldAmbient = Transaction.Current;  
     CommittableTransaction committableTransaction = new CommittableTransaction();  
     Transaction.Current = committableTransaction;  
  
     try  
     {  
          /* Perform transactional work here */  
          // No errors - commit transaction asynchronously  
          committableTransaction.BeginCommit(OnCommitted,null);  
     }  
     finally  
     {  
          //Restore the ambient transaction
          Transaction.Current = oldAmbient;  
     }  
}  
void OnCommitted(IAsyncResult asyncResult)  
{  
     CommittableTransaction committableTransaction;  
     committableTransaction = asyncResult as CommittableTransaction;
     Debug.Assert(committableTransaction != null);  
     try  
     {  
          using(committableTransaction)  
          {  
               committableTransaction.EndCommit(asyncResult);  
          }  
     }  
     catch(TransactionException e)  
     {  
          //Handle the failure to commit  
     }  
}  

请参阅