Share via


SQL Server と System.Transactions の統合 (ADO.NET)

.NET Framework バージョン 2.0 では、System.Transactions 名前空間を介してアクセスできるトランザクション フレームワークが導入されました。 このフレームワークでは、ADO.NET を含む .NET Framework に完全に統合された形でトランザクションが公開されます。

プログラミング上の強化に加えて、System.Transactions と ADO.NET の連係により、トランザクション処理が最適化されます。 昇格可能なトランザクションとは、必要に応じて完全な分散トランザクションに自動的に昇格する、軽量の (ローカル) トランザクションです。

ADO.NET 2.0 以降では、SQL Server 2005 を組み合わせて使用した場合、System.Data.SqlClient によって昇格可能なトランザクションがサポートされます。 昇格可能なトランザクションは、必要な場合以外、分散トランザクションのオーバーヘッドの増加を引き起こすことはありません。 昇格可能なトランザクションは自動的に処理され、開発者による介入は必要ありません。

昇格可能なトランザクションは、.NET Framework Data Provider for SQL Server (SqlClient) を SQL Server 2005 で使用する場合のみ使用可能です。

昇格可能なトランザクションの作成

.NET Framework Provider for SQL Server では昇格可能なトランザクションをサポートしており、.NET Framework System.Transactions 名前空間内のクラスを介して処理されます。 昇格可能なトランザクションでは、必要が生じるまで分散トランザクションの作成を延期することで、分散トランザクションが最適化されます。 必要なリソース マネージャーが 1 つだけである場合は、分散トランザクションは発生しません。

メモメモ

部分信頼のシナリオで分散トランザクションに昇格するには、DistributedTransactionPermission が必要です。詳細については、「Transaction Management Escalation」を参照してください。

昇格可能なトランザクションのシナリオ

分散トランザクションは一般的にシステム リソースを大量に消費するため、トランザクションでアクセスされるすべてのリソース マネージャーを統合する、Microsoft Distributed Transaction Coordinator (MS DTC) で管理されます。 昇格可能なトランザクションは特殊な形式の System.Transactions トランザクションで、単純な SQL Server 2005 トランザクションに処理を効果的に委任できます。 System.TransactionsSystem.Data.SqlClient、および SQL Server 2005 は、トランザクションの処理に関連する作業を調整し、必要に応じて、トランザクションを完全な分散トランザクションに昇格します。

昇格可能なトランザクションを使用する利点は、アクティブな TransactionScope トランザクションによって接続が開かれ、その他の接続が開いていない場合に、完全な分散トランザクションによるオーバーヘッドが生じることなく、トランザクションが軽量なトランザクションとしてコミットされることです。

接続文字列キーワード

ConnectionString プロパティではキーワード Enlist がサポートされており、System.Data.SqlClient によってトランザクションのコンテキストが検出され、分散トランザクションに接続が自動的に参加するかどうかが示されます。 Enlist=true である場合は、開いているスレッドの現在のトランザクション コンテキストに接続が自動的に参加します。 Enlist=false である場合、SqlClient 接続は分散トランザクションとは対話しません。 Enlist の既定値は true です。 Enlist が接続文字列で指定されていない場合、接続が開いたときに分散トランザクションがあればそれに自動的に参加します。

参加する System.Transactions トランザクションと接続の関連付けは、SqlConnection 接続文字列の Transaction Binding キーワードによって制御されます。 SqlConnectionStringBuilderTransactionBinding プロパティを介して利用することもできます。

次の表で使用可能な値について説明します。

キーワード

説明

Implicit Unbind

既定値。 完了時に接続がトランザクションからデタッチされ、自動コミット モードに切り替わります。

Explicit Unbind

トランザクションが閉じられるまで、接続がトランザクションにアタッチされたままになります。 関連付けられているトランザクションがアクティブでない場合や、Current と一致しない場合、接続は失敗します。

詳細については、「Implementing An Implicit Transaction Using Transaction Scope」を参照してください。

TransactionScope の使用

TransactionScope クラスでは、接続を分散トランザクションに暗黙的に参加させることで、コード ブロックがトランザクションのコード ブロックになります。 TransactionScope ブロックの末尾では、終了する前に Complete メソッドを呼び出す必要があります。 ブロックを終了すると、Dispose メソッドが呼び出されます。 例外がスローされてコードがスコープから外れている場合は、トランザクションがアボートされたと見なされます。

使用中のブロックを終了したときに TransactionScope オブジェクトで Dispose が呼び出されるように、using ブロックを使用することをお勧めします。 保留中のトランザクションのコミットまたはロールバックに失敗すると、TransactionScope の既定のタイムアウトが 1 分であるため、パフォーマンスが大幅に低下する場合があります。 using ステートメントを使用しない場合は、すべての処理を Try ブロックで実行し、Dispose メソッドを Finally ブロック内で明示的に呼び出す必要があります。

TransactionScope 内で例外が発生した場合、そのトランザクションは矛盾しているとしてマークされ、破棄されます。 トランザクションは、TransactionScope が破棄されるとロールバックされます。 例外が発生しない場合は、参加しているトランザクションがコミットされます。

メモメモ

既定では、TransactionScope クラスは、IsolationLevel が Serializable であるトランザクションを作成します。使用しているアプリケーションによっては、アプリケーション内での競合を回避するために、分離レベルを低下させる必要がある場合があります。

メモメモ

データベース リソースを大量に消費するため、分散トランザクション内で実行するのは更新、挿入、削除だけにすることをお勧めします。Select ステートメントを使用すると、データベース リソースが不必要にロックされることがあり、シナリオによっては選択にトランザクションを使用する必要がある場合があります。処理済みのその他のリソース マネージャーに関係する場合以外、データベース以外の処理はトランザクションのスコープ外で実行する必要があります。トランザクションのスコープ内で例外が発生するとトランザクションのコミットが防止されますが、TransactionScope クラスでは、作成したコードによってトランザクションのスコープ外で行われた変更はロールバックされません。トランザクションがロールバックされたときに何らかの動作を行うようにする場合は、IEnlistmentNotification インターフェイスを独自に実装し、トランザクションに明示的に参加する必要があります。

System.Transactions を使用する場合は、System.Transactions.dll への参照が必要になります。

次の関数は、TransactionScope ブロックでラップされている、異なる 2 つの SqlConnection オブジェクトで表された異なる 2 つの SQL Server インスタンスに対する、昇格可能なトランザクションを作成する方法を示しています。 このコードにより、using ステートメントを持つ TransactionScope ブロックが作成され、最初の接続が開き、TransactionScope に自動的に参加します。 このトランザクションは、完全な分散トランザクションとしてではなく、最初に軽量のトランザクションとして参加します。 2 つ目の接続は、1 つ目の接続のコマンドで例外がスローされなかった場合にのみ、TransactionScope に参加します。 2 つ目の接続が開かれると、トランザクションが完全な分散トランザクションに自動的に昇格されます。 Complete メソッドが呼び出され、例外がスローされなかった場合にのみ、トランザクションがコミットされます。 TransactionScope ブロックの任意の場所で例外がスローされると、Complete が呼び出されず、using ブロックの最後で TransactionScope が破棄されたときに分散トランザクションがロールバックされます。

' This function takes arguments for the 2 connection strings and commands in order
' to create a transaction involving two SQL Servers. It returns a value > 0 if the
' transaction committed, 0 if the transaction 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 RDBMS such as Oracle 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

    ' Create the TransactionScope in which to execute the commands, guaranteeing
    ' that both commands will commit or roll back as a single unit of work.
    Using scope As New TransactionScope()
        Using connection1 As New SqlConnection(connectString1)
            Try
                ' 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 by opening connection2 
                ' only when there is a chance that the transaction can commit.   
                Using connection2 As New SqlConnection(connectString2)
                    Try
                        ' The transaction is promoted 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)

                    Catch ex As Exception
                        ' Display information that command2 failed.
                        writer.WriteLine("returnValue for command2: {0}", returnValue)
                        writer.WriteLine("Exception Message2: {0}", ex.Message)
                    End Try
                End Using

            Catch ex As Exception
                ' Display information that command1 failed.
                writer.WriteLine("returnValue for command1: {0}", returnValue)
                writer.WriteLine("Exception Message1: {0}", ex.Message)
            End Try
        End Using

        ' If an exception has been thrown, Complete will 
        ' not be called and the transaction is rolled back.
        scope.Complete()
    End Using

    ' The returnValue is greater than 0 if the transaction committed.
    If returnValue > 0 Then
        writer.WriteLine("Transaction was committed.")
    Else
        ' You could write additional business logic here, notify the caller by
        ' throwing a TransactionAbortedException, or log the failure.
       writer.WriteLine("Transaction rolled back.")
     End If

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

    Return returnValue
End Function
// This function takes arguments for the 2 connection strings and commands in order
// to create a transaction involving two SQL Servers. It returns a value > 0 if the
// transaction committed, 0 if the transaction 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 RDBMS such as Oracle 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();

    // Create the TransactionScope in which to execute the commands, guaranteeing
    // that both commands will commit or roll back as a single unit of work.
    using (TransactionScope scope = new TransactionScope())
    {
        using (SqlConnection connection1 = new SqlConnection(connectString1))
        {
            try
            {
                // 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 by opening connection2 
                // only when there is a chance that the transaction can commit.   
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                    try
                    {
                        // The transaction is promoted 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);
                    }
                    catch (Exception ex)
                    {
                        // Display information that command2 failed.
                        writer.WriteLine("returnValue for command2: {0}", returnValue);
                        writer.WriteLine("Exception Message2: {0}", ex.Message);
                    }
            }
            catch (Exception ex)
            {
                // Display information that command1 failed.
                writer.WriteLine("returnValue for command1: {0}", returnValue);
                writer.WriteLine("Exception Message1: {0}", ex.Message);
            }
        }

        // If an exception has been thrown, Complete will not 
        // be called and the transaction is rolled back.
        scope.Complete();
    }

    // The returnValue is greater than 0 if the transaction committed.
    if (returnValue > 0)
    {
        writer.WriteLine("Transaction was committed.");
    }
    else
    {
        // You could write additional business logic here, notify the caller by
        // throwing a TransactionAbortedException, or log the failure.
        writer.WriteLine("Transaction rolled back.");
    }

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

    return returnValue;
}

参照

その他の技術情報

トランザクションと同時実行 (ADO.NET)

Features Provided By System.Transactions