使用事务范围实现隐式事务Implementing an Implicit Transaction using Transaction Scope

TransactionScope 类提供了一种简单方法,使您无需与事务自身进行交互,就可以在参与事务时对代码块进行标记。The TransactionScope class provides a simple way to mark a block of code as participating in a transaction, without requiring you to interact with the transaction itself. 事务范围可以自动选择和管理环境事务。A transaction scope can select and manage the ambient transaction automatically. 由于 TransactionScope 具有简单易用性和高效性,因此建议您在开发事务应用程序时使用该类。Due to its ease of use and efficiency, it is recommended that you use the TransactionScope class when developing a transaction application.

此外,还无需将资源显式登记到事务。In addition, you do not need to enlist resources explicitly with the transaction. 任何 System.Transactions 资源管理器(如 SQL Server 2005)都可以检测范围所创建的环境事务是否存在,并自动对其进行登记。Any System.Transactions resource manager (such as SQL Server 2005) can detect the existence of an ambient transaction created by the scope and automatically enlist.

创建事务范围Creating a transaction scope

下面的示例演示 TransactionScope 类的简单用法。The following sample shows a simple usage of the TransactionScope class.

// This function takes arguments for 2 connection strings and commands to create a transaction 
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the 
// transaction is rolled back. To test this code, you can connect to two different databases 
// on the same server by altering the connection string, or to another 3rd party RDBMS by 
// altering the code in the connection2 code block.
static public int CreateTransactionScope(
    string connectString1, string connectString2,
    string commandText1, string commandText2)
{
    // Initialize the return value to zero and create a StringWriter to display results.
    int returnValue = 0;
    System.IO.StringWriter writer = new System.IO.StringWriter();

    try
    {
        // Create the TransactionScope to execute the commands, guaranteeing
        // that both commands can commit or roll back as a single unit of work.
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                // Opening the connection automatically enlists it in the 
                // TransactionScope as a lightweight transaction.
                connection1.Open();

                // Create the SqlCommand object and execute the first command.
                SqlCommand command1 = new SqlCommand(commandText1, connection1);
                returnValue = command1.ExecuteNonQuery();
                writer.WriteLine("Rows to be affected by command1: {0}", returnValue);

                // If you get here, this means that command1 succeeded. By nesting
                // the using block for connection2 inside that of connection1, you
                // conserve server and network resources as connection2 is opened
                // only when there is a chance that the transaction can commit.   
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                {
                    // The transaction is escalated to a full distributed
                    // transaction when connection2 is opened.
                    connection2.Open();

                    // Execute the second command in the second database.
                    returnValue = 0;
                    SqlCommand command2 = new SqlCommand(commandText2, connection2);
                    returnValue = command2.ExecuteNonQuery();
                    writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
                }
            }

            // The Complete method commits the transaction. If an exception has been thrown,
            // Complete is not  called and the transaction is rolled back.
            scope.Complete();
        }
    }
    catch (TransactionAbortedException ex)
    {
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
    }

    // Display messages.
    Console.WriteLine(writer.ToString());

    return returnValue;
}
'  This function takes arguments for 2 connection strings and commands to create a transaction 
'  involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the 
'  transaction is rolled back. To test this code, you can connect to two different databases 
'  on the same server by altering the connection string, or to another 3rd party RDBMS  
'  by altering the code in the connection2 code block.
Public Function CreateTransactionScope( _
  ByVal connectString1 As String, ByVal connectString2 As String, _
  ByVal commandText1 As String, ByVal commandText2 As String) As Integer

    ' Initialize the return value to zero and create a StringWriter to display results.
    Dim returnValue As Integer = 0
    Dim writer As System.IO.StringWriter = New System.IO.StringWriter

    Try
    ' Create the TransactionScope to execute the commands, guaranteeing
    '  that both commands can commit or roll back as a single unit of work.
        Using scope As New TransactionScope()
            Using connection1 As New SqlConnection(connectString1)
                ' Opening the connection automatically enlists it in the 
                ' TransactionScope as a lightweight transaction.
                connection1.Open()

                ' Create the SqlCommand object and execute the first command.
                Dim command1 As SqlCommand = New SqlCommand(commandText1, connection1)
                returnValue = command1.ExecuteNonQuery()
                writer.WriteLine("Rows to be affected by command1: {0}", returnValue)

                ' If you get here, this means that command1 succeeded. By nesting
                ' the using block for connection2 inside that of connection1, you
                ' conserve server and network resources as connection2 is opened
                ' only when there is a chance that the transaction can commit.   
                Using connection2 As New SqlConnection(connectString2)
                    ' The transaction is escalated to a full distributed
                    ' transaction when connection2 is opened.
                    connection2.Open()

                    ' Execute the second command in the second database.
                    returnValue = 0
                    Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2)
                    returnValue = command2.ExecuteNonQuery()
                    writer.WriteLine("Rows to be affected by command2: {0}", returnValue)
                End Using
            End Using

        ' The Complete method commits the transaction. If an exception has been thrown,
        ' Complete is called and the transaction is rolled back.
        scope.Complete()
        End Using
    Catch ex As TransactionAbortedException
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
    End Try

    ' Display messages.
    Console.WriteLine(writer.ToString())

    Return returnValue
End Function

创建新的 TransactionScope 对象后,将启动事务范围。The transaction scope is started once you create a new TransactionScope object. 如代码示例中所示,建议使用 using 语句创建作用域。As illustrated in the code sample, it is recommended that you create scopes with a using statement. using 语句同时在和 Visual Basic 中C#可用,并且它的工作方式类似于try...finally块,以确保正确地处置作用域。The using statement is available both in C# and in Visual Basic, and works like a try...finally block to ensure that the scope is disposed of properly.

在实例化 TransactionScope 时,事务管理器确定哪些事务参与进来。When you instantiate TransactionScope, the transaction manager determines which transaction to participate in. 一旦确定,该范围将始终参与该事务。Once determined, the scope always participates in that transaction. 此决策基于两个因素:是否存在环境事务以及构造函数中 TransactionScopeOption 参数的值。The decision is based on two factors: whether an ambient transaction is present and the value of the TransactionScopeOption parameter in the constructor. 环境事务是指在其中执行代码的事务。The ambient transaction is the transaction within which your code executes. 可通过调用 Transaction.Current 类的静态 Transaction 属性,获取对环境事务的引用。You can obtain a reference to the ambient transaction by calling the static Transaction.Current property of the Transaction class. 有关如何使用此参数的详细信息,请参阅本主题的使用 TransactionScopeOption 管理事务流部分。For more information on how this parameter is used, see the Managing transaction flow using TransactionScopeOption section of this topic.

完成事务范围Completing a transaction scope

当应用程序完成它要在一个事务中执行的所有工作以后,您应当只调用一次 Complete 方法,来通知事务管理器它可以提交事务。When your application completes all the work it wants to perform in a transaction, you should call the Complete method only once to inform the transaction manager that it is acceptable to commit the transaction. 将对 Complete 的调用作为 using 块中的最后一个语句是很好的做法。It is very good practice to put the call to Complete as the last statement in the using block.

未能调用此方法会中止事务,因为事务管理器会将此解释为系统故障,或等效于事务范围内引发的异常。Failing to call this method aborts the transaction, because the transaction manager interprets this as a system failure, or equivalent to an exception thrown within the scope of the transaction. 但是,调用此方法并不保证会提交事务。However, calling this method does not guarantee that the transaction wil be committed. 它只是一种将状态通知给事务管理器的方式。It is merely a way of informing the transaction manager of your status. 调用 Complete 方法之后,就不能再通过 Current 属性访问环境事务,尝试这样做将会导致引发异常。After calling the Complete method, you can no longer access the ambient transaction by using the Current property, and attempting to do so will result in an exception being thrown.

如果 TransactionScope 对象最初创建了事务,则事务管理器会在 using 块的最后一个代码行后执行实际的提交事务工作。If the TransactionScope object created the transaction initially, the actual work of committing the transaction by the transaction manager occurs after the last line of code in the using block. 如果该对象未创建事务,则每当 Commit 对象的所有者调用 CommittableTransaction 时都会执行提交。If it did not create the transaction, the commit occurs whenever Commit is called by the owner of the CommittableTransaction object. 此时,事务管理器将调用资源管理器,并根据是否对 TransactionScope 对象调用 Complete 方法,通知它们提交或回滚。At that point the transaction manager calls the resource managers and informs them to either commit or rollback, based on whether the Complete method was called on the TransactionScope object.

using 语句可确保调用 Dispose 对象的 TransactionScope 方法,即使发生异常时也是如此。The using statement ensures that the Dispose method of the TransactionScope object is called even if an exception occurs. Dispose 方法标志着事务范围的结束。The Dispose method marks the end of the transaction scope. 在调用此方法之后所发生的异常不会影响事务。Exceptions that occur after calling this method may not affect the transaction. 此方法还将环境事务还原到其前一状态。This method also restores the ambient transaction to it previous state.

如果范围创建事务,则会引发 TransactionAbortedException,从而中止事务。A TransactionAbortedException is thrown if the scope creates the transaction, and the transaction is aborted. 如果事务管理器无法做出提交决定,则会引发 TransactionInDoubtExceptionA TransactionInDoubtException is thrown if the transaction manager cannot reach a Commit decision. 如果已提交事务,则不会引发异常。No exception is thrown if the transaction is committed.

回滚事务Rolling back a transaction

如果要回滚事务,则不应在事务范围中调用 Complete 方法。If you want to rollback a transaction, you should not call the Complete method within the transaction scope. 例如,可以在该范围中引发异常。For example, you can throw an exception within the scope. 这样,就会回滚该范围所参与的事务。The transaction in which it participates in will be rolled back.

使用 TransactionScopeOption 管理事务流Managing transaction flow using TransactionScopeOption

可通过调用一个方法来嵌套事务范围,该方法在使用其自己范围的方法中使用 TransactionScope,下面示例中的 RootMethod 方法就是前者这样的方法。Transaction scope can be nested by calling a method that uses a TransactionScope from within a method that uses its own scope, as is the case with the RootMethod method in the following example,

void RootMethod()
{
    using(TransactionScope scope = new TransactionScope())
    {
        /* Perform transactional work here */
        SomeMethod();
        scope.Complete();
    }
}

void SomeMethod()
{
    using(TransactionScope scope = new TransactionScope())
    {
        /* Perform transactional work here */
        scope.Complete();
    }
}

最顶层事务范围称为根范围。The top-most transaction scope is referred to as the root scope.

TransactionScope 类提供了多个重载构造函数,它们接受 TransactionScopeOption 类型的枚举,而该枚举定义范围的事务行为。The TransactionScope class provides several overloaded constructors that accept an enumeration of the type TransactionScopeOption, which defines the transactional behavior of the scope.

TransactionScope 对象有以下三个选项:A TransactionScope object has three options:

  • 联接环境事务,或者在环境事务不存在的情况下创建新的环境事务。Join the ambient transaction, or create a new one if one does not exist.

  • 成为新的根范围,也就是说,启动一个新事务并使该事务成为其自己范围中的新环境事务。Be a new root scope, that is, start a new transaction and have that transaction be the new ambient transaction inside its own scope.

  • 根本不参与事务。Not take part in a transaction at all. 因此没有环境事务。There is no ambient transaction as a result.

如果用 Required 实例化范围并且存在环境事务,则该范围会联接该事务。If the scope is instantiated with Required, and an ambient transaction is present, the scope joins that transaction. 相反,如果不存在环境事务,该范围就会创建新的事务并成为根范围。If, on the other hand, there is no ambient transaction, then the scope creates a new transaction, and become the root scope. 此为默认值。This is the default value. 在使用 Required 时,无论范围是根范围还是仅联接环境事务,该范围中的代码都不需要有不同的行为。When Required is used, the code inside the scope does not need to behave differently whether it is the root or just joining the ambient transaction. 该代码在这两种情况下的行为应相同。It should operate identically in both cases.

如果用 RequiresNew 实例化范围,则它始终为根范围。If the scope is instantiated with RequiresNew, it is always the root scope. 它会启动一个新事务,并且其事务成为该范围中的新环境事务。It starts a new transaction, and its transaction becomes the new ambient transaction inside the scope.

如果用 Suppress 实例化范围,则无论是否存在环境事务,范围都从不参与事务。If the scope is instantiated with Suppress, it never takes part in a transaction, regardless of whether an ambient transaction is present. 用此值实例化的范围始终使其环境事务为 nullA scope instantiated with this value always have null as its ambient transaction.

下表概括了上述这些选项。The above options are summarized in the following table.

TransactionScopeOptionTransactionScopeOption 参与环境事务Ambient Transaction 范围参与The scope takes part in
必需Required NoNo 参与新事务(将成为根范围)New Transaction (will be the root)
Requires NewRequires New NoNo 参与新事务(将成为根范围)New Transaction (will be the root)
SuppressSuppress NoNo 不参与任何事务No Transaction
必需Required Yes 参与环境事务Ambient Transaction
Requires NewRequires New Yes 参与新事务(将成为根范围)New Transaction (will be the root)
SuppressSuppress Yes 不参与任何事务No Transaction

TransactionScope 对象联接现有环境事务时,除非范围中止该事务,否则释放范围对象的操作可能并不会结束事务。When a TransactionScope object joins an existing ambient transaction, disposing of the scope object may not end the transaction, unless the scope aborts the transaction. 如果环境事务是由根范围创建的,则仅当释放根范围时,才会对事务调用 CommitIf the ambient transaction was created by a root scope, only when the root scope is disposed of, does Commit get called on the transaction. 如果事务是手动创建的,则它将在中止或由其创建者提交时结束。If the transaction was created manually, the transaction ends when it is either aborted, or committed by its creator.

下面的示例演示一个 TransactionScope 对象,该对象创建了三个嵌套的范围对象,并用不同的 TransactionScopeOption 值对其中每个对象进行了实例化。The following example shows a TransactionScope object that creates three nested scope objects, each instantiated with a different TransactionScopeOption value.

using(TransactionScope scope1 = new TransactionScope())
//Default is Required
{
    using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
    {
        //...
    }

    using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))   
    {
        //...  
    }
  
    using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        //...  
    }
}

下面的示例演示一个不包含任何环境事务的代码块,它使用 scope1 创建了一个新范围 (Required)。The example shows a code block without any ambient transaction creating a new scope (scope1) with Required. 范围 scope1 是根范围,因为它创建了一个新事务(事务 A),并使事务 A 成为环境事务。The scope scope1 is a root scope as it creates a new transaction (Transaction A) and makes Transaction A the ambient transaction. 然后 Scope1 再创建三个对象,每个对象都有不同的 TransactionScopeOption 值。Scope1 then creates three more objects, each with a different TransactionScopeOption value. 例如,scope2 是用 Required 创建的;由于存在环境事务,因此该范围联接 scope1 所创建的第一个事务。For example, scope2 is created with Required, and since there is an ambient transaction, it joins the first transaction created by scope1. 请注意,scope3 是新事务的根范围,而 scope4 则没有环境事务。Note that scope3 is the root scope of a new transaction, and that scope4 has no ambient transaction.

虽然 TransactionScopeOption 的默认值和最常用的值是 Required,但其他各值都有其独有的用途。Although the default and most commonly used value of TransactionScopeOption is Required, each of the other values has its unique purpose.

事务范围内的非事务性代码Non-transactional code inside a transaction scope

如果要保留代码部分执行的操作,并且不希望在操作失败的情况下中止环境事务,Suppress 会很有用。Suppress is useful when you want to preserve the operations performed by the code section, and do not want to abort the ambient transaction if the operations fail. 例如,在要执行日志记录或审核操作时,或者在无论环境事务提交还是中止都要将事件发布给订户时。For example, when you want to perform logging or audit operations, or when you want to publish events to subscribers regardless of whether your ambient transaction commits or aborts. 使用此值,可以在事务范围中包含非事务代码段,如下面的示例所示。This value allows you to have a non-transactional code section inside a transaction scope, as shown in the following example.

using(TransactionScope scope1 = new TransactionScope())
{
    try
    {
        //Start of non-transactional section
        using(TransactionScope scope2 = new
            TransactionScope(TransactionScopeOption.Suppress))  
        {  
            //Do non-transactional work here  
        }  
        //Restores ambient transaction here
   }
   catch {}  
   //Rest of scope1
}

在嵌套范围中投票Voting inside a nested scope

虽然嵌套范围可联接根范围的环境事务,但在嵌套范围中调用 Complete 对根范围没有影响。Although a nested scope can join the ambient transaction of the root scope, calling Complete in the nested scope has no affect on the root scope. 仅当从根范围到最后一个嵌套范围的所有范围都投票决定提交事务时,才会提交该事务。Only if all the scopes from the root scope down to the last nested scope vote to commit the transaction, will the transaction be committed. 由于环境事务将立即中止,因此嵌套范围中的 Complete 调用并不全都会影响根范围。Not calling Complete in a nested scope will affect the root scope as the ambient transaction will immediately be aborted.

设置 TransactionScope 超时Setting the TransactionScope timeout

TransactionScope 的有些重载构造函数接受 TimeSpan 类型的值,该值用于控制事务的超时。Some of the overloaded constructors of TransactionScope accept a value of type TimeSpan, which is used to control the timeout of the transaction. 超时设置为零时表示超时无限长。A timeout set to zero means an infinite timeout. 无限长的超时主要对调试有用,调试过程中可能要经由逐句通过代码来隔离业务逻辑中的问题,并且在尝试确定问题期间不希望所调试的事务超时。Infinite timeout is useful mostly for debugging, when you want to isolate a problem in your business logic by stepping through your code, and you do not want the transaction you debug to time out while you attempt to locate the problem. 在所有其他情况下使用无限长的超时时一定要格外小心,因为它会覆盖防止事务死锁的保护。Be extremely careful using the infinite timeout value in all other cases, because it overrides the safeguards against transaction deadlocks.

TransactionScope 超时值通常设置为以下两种情况下的默认值之外的值。You typically set the TransactionScope timeout to values other than default in two cases. 第一种情况是在开发期间要测试应用程序处理中止事务的方式时。The first is during development, when you want to test the way your application handles aborted transactions. 将超时值设置为较小值(如 1 毫秒)时,会导致事务失败,因而会看到错误处理代码。By setting the timeout to a small value (such as one millisecond), you cause your transaction to fail and can thus observe your error handling code. 将超时值设置为小于默认超时值的第二种情况是:认为在导致死锁的资源争用中涉及到范围时。The second case in which you set the value to be less than the default timeout is when you believe that the scope is involved in resource contention, resulting in deadlocks. 在这种情况下,需要尽快地中止事务,而不能等到达到默认超时值。In that case, you want to abort the transaction as soon as possible and not wait for the default timeout to expire.

在范围联接环境事务但所指定的超时值小于为该环境事务所设置的超时值时,会在 TransactionScope 对象上强制实施较小的超时值,并且该范围必须在指定的嵌套时间内结束,否则会自动中止事务。When a scope joins an ambient transaction but specifies a smaller timeout than the one the ambient transaction is set to, the new, shorter timeout is enforced on the TransactionScope object, and the scope must end within the nested time specified, or the transaction is automatically aborted. 如果嵌套范围的超时值大于环境事务的超时值,则前者无效。If the nested scope's timeout is more than that of the ambient transaction, it has no effect.

设置 TransactionScope 隔离级别Setting the TransactionScope isolation level

除超时值之外,TransactionScope 的有些重载构造函数还接受 TransactionOptions 类型的结构,用于指定隔离级别。Some of the overloaded constructors of TransactionScope accept a structure of type TransactionOptions to specify an isolation level, in addition to a timeout value. 默认情况下,事务在隔离级别设置为 Serializable 的情况下执行。By default, the transaction executes with isolation level set to Serializable. 通常对频繁执行读取的系统选择 Serializable 之外的隔离级别。Selecting an isolation level other than Serializable is commonly used for read-intensive systems. 这需要全面地了解事务处理理论、事务本身的语义、所涉及的并发问题以及系统一致性的结果。This requires a solid understanding of transaction processing theory and the semantics of the transaction itself, the concurrency issues involved, and the consequences for system consistency.

此外,并不是所有的资源管理器都支持所有的隔离级别,并且它们可选择参与高于所配置级别的事务。In addition, not all resource managers support all levels of isolation, and they may elect to take part in the transaction at a higher level than the one configured.

Serializable 之外的所有级别都容易因其他事务访问相同的信息而产生不一致问题。Every isolation level besides Serializable is susceptible to inconsistency resulting from other transactions accessing the same information. 不同隔离级别之间的差异在于它们使用读取和写入锁定的方式。The difference between the different isolation levels is in the way read and write locks are used. 可在事务访问资源管理器中的数据时保持锁定,也可在提交或中止事务之前保持锁定。A lock can be held only when the transaction accesses the data in the resource manager, or it can be held until the transaction is committed or aborted. 从吞吐量的角度来说,前者比较适合;而从一致性角度来说,后者比较适合。The former is better for throughput, the latter for consistency. 这两种锁定和两种操作(读取/写入)提供了四种基本隔离级别。The two kinds of locks and the two kinds of operations (read/write) give four basic isolation levels. 有关更多信息,请参见IsolationLevelSee IsolationLevel for more information.

在使用嵌套的 TransactionScope 对象时,如果要联接环境事务,则必须将所有嵌套范围配置为使用完全相同的隔离级别。When using nested TransactionScope objects, all nested scopes must be configured to use exactly the same isolation level if they want to join the ambient transaction. 如果嵌套的 TransactionScope 对象尝试联接环境事务但却指定了不同的隔离级别,则会引发 ArgumentExceptionIf a nested TransactionScope object tries to join the ambient transaction yet it specifies a different isolation level, an ArgumentException is thrown.

与 COM+ 交互Interop with COM+

在创建新的 TransactionScope 实例时,可以在某一构造函数中使用 EnterpriseServicesInteropOption 枚举来指定与 COM+ 交互的方式。When you create a new TransactionScope instance, you can use the EnterpriseServicesInteropOption enumeration in one of the constructors to specify how to interact with COM+. 有关此操作的详细信息,请参阅与企业服务和 COM + 事务的互操作性For more information on this, see Interoperability with Enterprise Services and COM+ Transactions.

请参阅See also