使用交易範圍實作隱含交易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語句適用于C#和中的 Visual Basic, 而且運作方式與try ...最後, 封鎖以確保正確處置範圍。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.

如果物件一開始就建立交易, 則交易管理員認可交易的實際工作會在 using 區塊中的最後一行程式碼之後發生。 TransactionScopeIf 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. 此時, 交易管理員會呼叫資源管理員, 並根據是否CompleteTransactionScope物件上呼叫方法, 通知它們認可或復原。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.

如果範圍建立了交易,而且交易中止,則會擲回 TransactionAbortedExceptionA 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. 以這個值具現化的範圍一律會有null做為其環境交易。A 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 No 新交易 (將為根)New Transaction (will be the root)
必須是新交易Requires New No 新交易 (將為根)New Transaction (will be the root)
隱藏Suppress No 無交易No Transaction
必要Required Yes 環境交易Ambient Transaction
必須是新交易Requires New Yes 新交易 (將為根)New Transaction (will be the root)
隱藏Suppress 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