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をインスタンス化すると、トランザクション マネージャーによって、参加するトランザクションが決定されます。 決定されると、スコープは常にそのトランザクションに参加します。 この決定は、アンビエント トランザクションが存在するかどうかと、コンストラクター内の TransactionScopeOption
パラメーターの値という 2 つの要因に基づいています。 アンビエント トランザクションは、コードが実行されるトランザクションです。 アンビエント トランザクションへの参照を取得するには、Transaction クラスの静的Transaction.Current プロパティを呼び出します。 このパラメーターの使用方法の詳細については、このトピックの 「TransactionScopeOption を使用したトランザクション フローの管理 」セクションを参照してください。
トランザクション スコープの完了
アプリケーションがトランザクションで実行するすべての作業を完了したら、 TransactionScope.Complete メソッドを 1 回だけ呼び出して、トランザクションのコミットが許容されることをトランザクション マネージャーに通知する必要があります。
Completeの呼び出しを最後のステートメントとして using
ブロックに配置することをお勧めします。
トランザクション マネージャーはこれをシステム障害として解釈するか、トランザクションのスコープ内でスローされた例外と同等であると解釈するため、このメソッドを呼び出さなかった場合、トランザクションが中止されます。 ただし、このメソッドを呼び出しても、トランザクションがコミットされるとは限りません。 これは単に、トランザクション マネージャーに状態を通知する方法にすぎません。 Complete メソッドを呼び出した後は、Current プロパティを使用してアンビエント トランザクションにアクセスできなくなり、これを試みると例外がスローされます。
TransactionScope オブジェクトが最初にトランザクションを作成した場合、トランザクション マネージャーによってトランザクションをコミットする実際の作業は、using
ブロック内のコードの最後の行の後に発生します。 トランザクションを作成しなかった場合、CommittableTransaction オブジェクトの所有者によってCommitが呼び出されるたびにコミットが発生します。 その時点で、トランザクション マネージャーはリソース マネージャーを呼び出し、 Complete メソッドが TransactionScope オブジェクトで呼び出されたかどうかに基づいて、コミットまたはロールバックを通知します。
using
ステートメントを使用すると、例外が発生した場合でも、TransactionScope オブジェクトのDispose メソッドが確実に呼び出されます。
Dispose メソッドは、トランザクション スコープの末尾をマークします。 このメソッドを呼び出した後に発生する例外は、トランザクションに影響しない可能性があります。 また、このメソッドはアンビエント トランザクションを以前の状態に復元します。
スコープによってトランザクションが作成され、トランザクションが中止されると、 TransactionAbortedException がスローされます。 トランザクション マネージャーがコミットの決定に達できない場合、 TransactionInDoubtException がスローされます。 トランザクションがコミットされた場合、例外はスローされません。
トランザクションのロールバック
トランザクションをロールバックする場合は、トランザクション スコープ内で Complete メソッドを呼び出さないでください。 たとえば、スコープ内で例外をスローできます。 参加しているトランザクションはロールバックされます。
TransactionScopeOption を使用したトランザクション フローの管理
トランザクション スコープは、次の例の RootMethod
メソッドの場合と同様に、独自のスコープを使用するメソッド内からTransactionScopeを使用するメソッドを呼び出すことによって入れ子にすることができます。
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 | アンビエント トランザクション | スコープは〘〘 |
---|---|---|
必須 | いいえ | 新しいトランザクション (ルートになります) |
[新規が必要] | いいえ | 新しいトランザクション (ルートになります) |
抑制する | いいえ | トランザクションなし |
必須 | イエス | アンビエント トランザクション |
[新規が必要] | イエス | 新しいトランザクション (ルートになります) |
抑制する | イエス | トランザクションなし |
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))
{
//...
}
}
この例では、アンビエント トランザクションを使用せずに、Requiredを使用して新しいスコープ (scope1
) を作成するコード ブロックを示します。 スコープ scope1
は、新しいトランザクション (トランザクション A) を作成し、トランザクション A をアンビエント トランザクションにするルート スコープです。
Scope1
次に、それぞれ異なる TransactionScopeOption 値を持つ 3 つのオブジェクトを作成します。 たとえば、 scope2
は Requiredで作成され、アンビエント トランザクションがあるため、 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型の値を受け取るものもあります。 タイムアウトが 0 に設定されている場合は、無限タイムアウトを意味します。 無限タイムアウトは、ほとんどの場合、コードをステップ実行してビジネス ロジックの問題を特定し、問題の特定中にデバッグするトランザクションをタイムアウトにしたくない場合に、デバッグに役立ちます。 無限タイムアウト値は、トランザクションのデッドロックに対するセーフガードをオーバーライドするため、他のすべてのケースでは非常に注意してください。
通常、 TransactionScope タイムアウトは、2 つのケースで既定値以外の値に設定します。 1 つ目は、アプリケーションが中止されたトランザクションを処理する方法をテストする開発中です。 タイムアウトを小さな値 (1 ミリ秒など) に設定すると、トランザクションが失敗し、エラー処理コードを確認できます。 値を既定のタイムアウトより小さく設定する 2 番目のケースは、スコープがリソースの競合に関係し、デッドロックが発生すると考えられる場合です。 その場合は、トランザクションをできるだけ早く中止し、既定のタイムアウトの有効期限が切れるのを待つ必要はありません。
スコープがアンビエント トランザクションに参加するが、アンビエント トランザクションが設定されているタイムアウトよりも小さいタイムアウトを指定すると、新しい短いタイムアウトが TransactionScope オブジェクトに適用され、スコープは指定された入れ子になった時間内に終了する必要があります。または、トランザクションが自動的に中止されます。 入れ子になったスコープのタイムアウトがアンビエント トランザクションのタイムアウトを超える場合、影響はありません。
TransactionScope 分離レベルの設定
TransactionScopeのオーバーロードされたコンストラクターの中には、タイムアウト値に加えて、分離レベルを指定するためにTransactionOptions型の構造体を受け入れるものもあります。 既定では、トランザクションは分離レベルを Serializable に設定して実行されます。 Serializable以外の分離レベルの選択は、読み取り集中型システムでよく使用されます。 これには、トランザクション処理の理論とトランザクション自体のセマンティクス、関連するコンカレンシーの問題、およびシステム整合性の結果をしっかりと理解する必要があります。
さらに、すべてのリソース マネージャーがすべてのレベルの分離をサポートしているわけではありません。また、構成されたレベルよりも高いレベルでトランザクションに参加することを選択できます。
Serializable以外のすべての分離レベルは、同じ情報にアクセスする他のトランザクションによって生じる不整合の影響を受けやすくなります。 異なる分離レベルの違いは、読み取りロックと書き込みロックの使用方法にあります。 ロックは、トランザクションがリソース マネージャーのデータにアクセスする場合にのみ保持することも、トランザクションがコミットまたは中止されるまで保持することもできます。 前者はスループットの方が良く、後者は一貫性のために優れています。 2 種類のロックと 2 種類の操作 (読み取り/書き込み) により、4 つの基本的な分離レベルが提供されます。 詳細については、IsolationLevel を参照してください。
入れ子になった TransactionScope オブジェクトを使用する場合、アンビエント トランザクションを結合する場合は、入れ子になったすべてのスコープがまったく同じ分離レベルを使用するように構成する必要があります。 入れ子になった TransactionScope オブジェクトがアンビエント トランザクションに参加しようとしても、別の分離レベルを指定すると、 ArgumentException がスローされます。
COM+ との相互運用
新しい TransactionScope インスタンスを作成するときに、いずれかのコンストラクターで EnterpriseServicesInteropOption 列挙を使用して、COM+ との対話方法を指定できます。 詳細については、「 Enterprise Services および COM+ Transactions との相互運用性」を参照してください。
こちらも参照ください
.NET