次の方法で共有


トランザクション スコープを使用した暗黙的なトランザクションの実装

TransactionScope クラスを使用すると、コード ブロックがトランザクションに参加しているものとして簡単にマークすることができ、トランザクション自体と対話する必要がありません。 トランザクション スコープは、アンビエント トランザクションを自動的に選択して管理することができます。 トランザクション アプリケーションを開発する際は、使いやすさと効率の点から、TransactionScope クラスを使用することをお勧めします。

また、リソースをトランザクションに明示的に参加させる必要がありません。 System.Transactions リソース マネージャー (SQL Server 2005 など) は、スコープによって作成されたアンビエント トランザクションを検出して、自動的に参加することができます。

トランザクション スコープの作成

次のサンプルは、TransactionScope クラスの簡単な使用法を示しています。

// 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 オブジェクトを作成すると、トランザクション スコープが開始されます。 コード サンプルで示されているように、using ステートメントを使用してスコープを作成することをお勧めします。 using ステートメントは C# と Visual Basic の両方で使用でき、try...finally ブロックと同じように機能して、スコープが適切に破棄されるようにします。

TransactionScope をインスタンス化すると、参加するトランザクションがトランザクション マネージャーによって決定されます。 いったん決定されると、このスコープは常にそのトランザクションに参加します。 この決定は 2 つの要因に基づいて行われます。1 つはアンビエント トランザクションが存在するかどうか、もう 1 つはコンストラクターの TransactionScopeOption パラメーターの値です。 アンビエント トランザクションとは、実行するコードが含まれているトランザクションのことです。 Transaction.Current クラスの静的 Transaction プロパティを呼び出すことによってアンビエント トランザクションへの参照を取得できます。 このパラメーターの使用法の詳細については、このトピックの「TransactionScopeOption を使用したトランザクション フローの管理」を参照してください。

トランザクション スコープの完了

アプリケーションがトランザクション内で実行する必要のあるすべての作業を完了したら、トランザクションをコミットできることをトランザクション マネージャーに知らせるために、TransactionScope.Complete メソッドを一度だけ呼び出す必要があります。 using ブロックの最後のステートメントとして Complete の呼び出しを配置するのは非常によい方法です。

この呼び出しを行わないと、トランザクション マネージャーは、システム障害が発生したかまたはトランザクションのスコープ内で例外がスローされたと解釈するため、トランザクションが中止されます。 ただし、このメソッドを呼び出したからといって必ずしもトランザクションのコミットが保証されるわけではありません。 これはトランザクション マネージャーにステータスを通知する手段にすぎません。 Complete メソッドを呼び出した後は、Current プロパティを使用してアンビエント トランザクションにアクセスできなくなります。アクセスしようとすると例外がスローされます。

TransactionScope オブジェクトによって最初にトランザクションが作成された場合、トランザクション マネージャーでトランザクションが実際にコミットされる処理は、using ブロックの最後のコード行の後で行われます。 このオブジェクトによってトランザクションが作成されていない場合、Commit オブジェクトの所有者によって CommittableTransaction が呼び出されるたびにコミットが発生します。 その時点で、トランザクション マネージャーによってリソース マネージャーが呼び出され、TransactionScope オブジェクトの Complete メソッドが呼び出されたかどうかに基づいて、コミットするかロールバックするかが通知されます。

using ステートメントを使用すると、例外が発生した場合でも必ず TransactionScope オブジェクトの Dispose メソッドが呼び出されます。 Dispose メソッドは、トランザクション スコープの末尾を表します。 このメソッドの呼び出し後に発生した例外は、トランザクションに影響しない場合があります。 また、このメソッドはアンビエント トランザクションを前の状態に復元します。

スコープがトランザクションを作成し、そのトランザクションが中止された場合は、TransactionAbortedException がスローされます。 トランザクション マネージャーがコミットを判断できない場合は、TransactionInDoubtException がスローされます。 トランザクションがコミットされた場合は、例外はスローされません。

トランザクションのロールバック

トランザクションをロールバックする場合は、トランザクション スコープ内で Complete メソッドを呼び出さないようにしてください。 たとえば、スコープ内で例外をスローすると、 スコープが参加しているトランザクションがロールバックされます。

TransactionScopeOption を使用したトランザクション フローの管理

次の例にある TransactionScope メソッドのように、独自のスコープを使用するメソッド内から、RootMethod を使用するメソッドを呼び出すことによって、トランザクション スコープを入れ子にすることができます。

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();
    }
}

最上位のトランザクション スコープをルート スコープと呼びます。

TransactionScope クラスには、スコープのトランザクション動作を定義する TransactionScopeOption 型の列挙体を受け入れる、いくつかのオーバーロードされたコンストラクターがあります。

TransactionScope オブジェクトには次の 3 つのオプションがあります。

  • アンビエント トランザクションに参加します (存在しない場合は新規に作成します)。

  • 新しいルート スコープになります。つまり、新しいトランザクションを開始して、そのトランザクションをそれ自身のスコープ内の新しいアンビエント トランザクションにします。

  • どのトランザクションにも参加しません。 その結果、アンビエント トランザクションは存在しません。

スコープが Required でインスタンス化された場合、アンビエント トランザクションが存在しているときは、スコープはそのトランザクションに参加します。 一方、アンビエント トランザクションが存在しないときは、スコープは新しいトランザクションを作成して、ルート スコープになります。 これが既定値です。 Required を使用した場合、スコープがルートのときでも、アンビエント トランザクションに参加するだけのときでも、スコープ内のコードは異なる動作をする必要がありません。 いずれの場合にもスコープ内のコードは同じ動作をします。

スコープが RequiresNew でインスタンス化された場合は、常にルート スコープです。 スコープが新しいトランザクションを開始し、そのトランザクションがスコープ内の新しいアンビエント トランザクションになります。

スコープが Suppress でインスタンス化された場合は、アンビエント トランザクションの有無にかかわらず、スコープがトランザクションに参加することはありません。 この値でインスタンス化されたスコープの場合は、スコープのアンビエント トランザクションは常に null になります。

上記のオプションを要約すると、次の表のようになります。

TransactionScopeOption アンビエント トランザクション スコープの参加
必須 いいえ 新規トランザクション (ルートになる)
RequiresNew いいえ 新規トランザクション (ルートになる)
Suppress いいえ トランザクションなし
必須 はい アンビエント トランザクション
RequiresNew はい 新規トランザクション (ルートになる)
Suppress はい トランザクションなし

TransactionScope オブジェクトが既存のアンビエント トランザクションに参加した場合、スコープ オブジェクトを破棄してもトランザクションが終了しないことがあります (スコープがトランザクションを中止した場合を除く)。 アンビエント トランザクションがルート スコープによって作成されたものである場合は、ルート スコープが破棄されたときのみ、トランザクションの Commit が呼び出されます。 トランザクションが手動で作成されたものである場合は、中止されるか、その作成者によってコミットされたときに、そのトランザクションは終了します。

次の例は、入れ子になった 3 つのスコープ オブジェクトを作成する TransactionScope オブジェクトを示しています。各スコープ オブジェクトは異なる TransactionScopeOption 値でインスタンス化しています。

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))
    {
        //...  
    }
}

このコード例では、アンビエント トランザクションがない状態で、新しいスコープ scope1Required で作成しています。 スコープ scope1 は、新しいトランザクション (トランザクション A) を作成し、トランザクション A をアンビエント トランザクションにするため、ルート スコープになります。 Scope1 では次に、さらに 3 つのオブジェクトが、それぞれ異なる TransactionScopeOption 値で作成されます。 たとえば、scope2Required で作成されますが、アンビエント トランザクションがあるため、scope1 によって作成された最初のトランザクションに参加します。 scope3 は新しいトランザクションのルート スコープです。scope4 にはアンビエント トランザクションがありません。

TransactionScopeOption の既定値でかつ最もよく使用される値は Required ですが、その他の各値にはそれぞれ固有の用途があります。

トランザクション スコープ内の非トランザクション コード

Suppress は、コード セクションによって実行されている操作を保持する必要があり、操作が失敗した場合にアンビエント トランザクションを中止したくない場合に便利です。 たとえば、ログの記録や監査操作を実行する場合や、アンビエント トランザクションがコミットしても中止してもサブスクライバーにイベントを公開する場合などです。 次の例で示すように、この値を使用すれば、トランザクション スコープ内に非トランザクション コード セクションを置くことができます。

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
}

入れ子になったスコープ内での選択

入れ子になったスコープはルート スコープのアンビエント トランザクションに参加できますが、入れ子になったスコープ内で Complete を呼び出してもルート スコープには影響がありません。 トランザクションは、ルート スコープから入れ子になった最後のスコープまで、すべてのスコープでトランザクションのコミットが選択された場合にのみ、コミットされます。 入れ子になったスコープで Complete を呼び出さないと、アンビエント トランザクションが即時に中止されるため、ルート スコープに影響します。

TransactionScope タイムアウトの設定

TransactionScope のオーバーロードされたコンストラクターのいくつかは、トランザクションのタイムアウトを制御するために使用される TimeSpan 型の値を受け入れます。 タイムアウトをゼロに設定すると、タイムアウトは無期限になります。 無期限のタイムアウトは主にデバッグに役立ちます。つまり、コードをステップ実行することによってビジネス ロジックの問題を切り分け、問題の究明を試みている間はデバッグするトランザクションがタイムアウトにならないようにすることができます。 タイムアウト値を無期限にすると、トランザクションのデッドロックに対する保護機能がオーバーライドされるため、上記以外の目的でこれを使用する場合は十分注意する必要があります。

次の 2 つの場合は、通常、TransactionScope タイムアウトを既定以外の値に設定します。 第 1 は、開発時に、中止されたトランザクションをアプリケーションがどう処理するかをテストする場合です。 タイムアウトを小さい値 (1 ミリ秒など) に設定すると、トランザクションが失敗するため、エラー処理コードを確認できます。 第 2 は、スコープがリソース競合に関与した結果、デッドロックの発生が考えられるときに、既定のタイムアウト値より小さい値に設定する場合です。 この場合は、できるだけ早くトランザクションを中止して、既定のタイムアウトが満了するのを待ちません。

アンビエント トランザクションに参加するスコープが、アンビエント トランザクションのタイムアウト設定値より小さいタイムアウト値を指定すると、TransactionScope オブジェクトに対して、後から指定した短い方のタイムアウトが適用され、スコープは指定した時間内に終了する必要があります。終了しない場合、トランザクションは自動的に中止されます。 入れ子になったスコープのタイムアウトがアンビエント トランザクションのタイムアウトより長い場合は、効果はありません。

TransactionScope 分離レベルの設定

TransactionScope のオーバーロードされたコンストラクターのいくつかは、タイムアウト値の他にも、分離レベルを指定する TransactionOptions 型の構造体を受け入れます。 既定では、トランザクションは Serializable に設定された分離レベルで実行されます。 Serializable 以外の分離レベルは、読み取り集中型のシステムの場合によく使用されます。 そのためには、トランザクション処理理論とトランザクション自体の動作、関連するコンカレンシーの問題、およびシステム整合性の影響をよく理解する必要があります。

さらに、すべてのリソース マネージャーがすべての分離レベルをサポートするわけではなく、構成されたレベルより高いレベルのトランザクションへの参加をリソース マネージャーが選択する場合もあります。

Serializable を除くすべての分離レベルは、他のトランザクションが同じ情報にアクセスすることで生じる不整合の影響を受けやすくなっています。 分離レベルごとの違いは、読み取りロックおよび書き込みロックの使用方法にあります。 ロックには、トランザクションがリソース マネージャーのデータにアクセスするときのみ保持できるか、トランザクションがコミットまたは中止されるまで保持できるかの 2 種類があります。 前者はスループットの面で優れており、後者は整合性の面で優れています。 2 種類のロックと 2 種類の操作 (読み取り/書き込み) から、4 つの基本分離レベルが構成されます。 詳細については、「IsolationLevel」を参照してください。

入れ子になった TransactionScope オブジェクトを使用する場合、入れ子になったすべてのスコープがアンビエント トランザクションに参加するときには、必ず同じ分離レベルを使用するようにそれらのスコープを構成する必要があります。 入れ子になった TransactionScope オブジェクトがアンビエント トランザクションに参加するときに、別の分離レベルが指定された場合は、ArgumentException がスローされます。

COM+ との相互運用

新しい TransactionScope インスタンスを作成する際には、いずれかのコンストラクターで EnterpriseServicesInteropOption 列挙体を使用して、COM+ との対話方法を指定することができます。 詳しくは、「Enterprise Services および COM+ トランザクションとの相互運用性」をご覧ください。

関連項目