CommittableTransaction を使用した明示的なトランザクションの実装
CommittableTransaction クラスは、TransactionScope クラスが暗黙的に使用されるのと対照的に、アプリケーションがトランザクションを明示的に使用する方法を提供します。 これは、複数の関数呼び出しまたは複数のスレッド呼び出しで同じトランザクションを使用するアプリケーションで役立ちます。 TransactionScope クラスとは異なり、アプリケーション作成者はトランザクションをコミットまたは中止するために、具体的に Commit メソッドまたは Rollback メソッドを呼び出す必要があります。
CommittableTransaction クラスの概要
CommittableTransaction クラスは、Transaction クラスから派生するため、後者のすべての機能を提供します。 特に有用なのは、Rollback クラスの Transaction メソッドで、これは CommittableTransaction オブジェクトのロールバックにも使用できます。
Transaction クラスは CommittableTransaction クラスに類似していますが、Commit
メソッドは提供しません。 これにより、トランザクションのコミット時に制御を継続しながら、トランザクション オブジェクト (またはその複製) を他のメソッド (他のスレッド上に存在する可能性もあり) に渡すことができます。 呼び出されたコードでは、トランザクションの参加や処理の選択を行えますが、トランザクションをコミットできるのは CommittableTransaction オブジェクトの作成者のみです。
CommittableTransaction クラスを使用する場合には、次のことに注意する必要があります。
CommittableTransaction トランザクションを作成しても、アンビエント トランザクションは設定されません。 リソース マネージャーが必要に応じて正しいトランザクション コンテキストで動作することを保証するには、アンビエント トランザクションを具体的に設定およびリセットする必要があります。 現在のアンビエント トランザクションを設定するには、グローバルな Current オブジェクトの静的な Transaction プロパティを設定します。
CommittableTransaction オブジェクトは再利用できません。 CommittableTransaction オブジェクトがコミットまたはロールバックされると、トランザクションで再び使用することはできません。 つまり、現在のアンビエント トランザクション コンテキストとして設定することはできません。
CommittableTransaction の作成
新しい CommittableTransaction を作成してコミットする例を次に示します。
//Create a committable transaction
tx = new CommittableTransaction();
SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
SqlCommand myCommand = new SqlCommand();
//Open the SQL connection
myConnection.Open();
//Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx);
myCommand.Connection = myConnection;
// Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
myCommand.ExecuteNonQuery();
// Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
myCommand.ExecuteNonQuery();
// Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
myCommand.ExecuteNonQuery();
// Commit or rollback the transaction
while (true)
{
Console.Write("Commit or Rollback? [C|R] ");
ConsoleKeyInfo c = Console.ReadKey();
Console.WriteLine();
if ((c.KeyChar == 'C') || (c.KeyChar == 'c'))
{
tx.Commit();
break;
}
else if ((c.KeyChar == 'R') || (c.KeyChar == 'r'))
{
tx.Rollback();
break;
}
}
myConnection.Close();
tx = null;
tx = New CommittableTransaction
Dim myConnection As New SqlConnection("server=(local)\SQLExpress;Integrated Security=SSPI;database=northwind")
Dim myCommand As New SqlCommand()
'Open the SQL connection
myConnection.Open()
'Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx)
myCommand.Connection = myConnection
'Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"
myCommand.ExecuteNonQuery()
'Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"
myCommand.ExecuteNonQuery()
'Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"
myCommand.ExecuteNonQuery()
'Commit or rollback the transaction
Dim c As ConsoleKeyInfo
While (True)
Console.Write("Commit or Rollback? [C|R] ")
c = Console.ReadKey()
Console.WriteLine()
If (c.KeyChar = "C") Or (c.KeyChar = "c") Then
tx.Commit()
Exit While
ElseIf ((c.KeyChar = "R") Or (c.KeyChar = "r")) Then
tx.Rollback()
Exit While
End If
End While
myConnection.Close()
tx = Nothing
CommittableTransaction のインスタンスを作成しても、アンビエント トランザクション コンテキストは自動的に設定されません。 したがって、リソース マネージャーに対する操作は、そのトランザクションの一部に含まれません。 アンビエント トランザクションを設定または取得するには、グローバル Current オブジェクトの静的な Transaction プロパティを使用します。アプリケーションでは、リソース マネージャーがトランザクションに参加できるようにするために、手動でこのプロパティを設定する必要があります。 古いアンビエント トランザクションを保存し、CommittableTransaction オブジェクトの使用が終了したら復元することをお勧めします。
トランザクションをコミットするには、Commit メソッドを明示的に呼び出す必要があります。 トランザクションをロールバックするには、Rollback メソッドを呼び出す必要があります。 CommittableTransaction がコミットまたはロールバックされるまで、そのトランザクションに関連するすべてのリソースはロックされたままであることに注意してください。
CommittableTransaction オブジェクトは、複数の関数呼び出しおよびスレッドにわたって使用できます。 ただし、例外を処理し、特に障害発生時に Rollback(Exception) メソッドを呼び出すことはアプリケーション開発者の責任です。
非同期コミット
CommittableTransaction クラスは、トランザクションを非同期にコミットするための機構も提供します。 トランザクションのコミットは、複数回のデータベース アクセスや潜在的なネットワーク待機時間が含まれる場合、かなりの時間を要することがあります。 高いスループットのアプリケーションでデッドロックを回避するため、非同期コミットを使用してできるだけ早期にトランザクションの処理を完了し、バックグラウンド タスクとしてコミット処理を実行できます。 このために、BeginCommit クラスの EndCommit メソッドおよび CommittableTransaction メソッドを使用できます。
BeginCommit を呼び出すことで、スレッド プールからのスレッドにコミット ホールドアップをディスパッチできます。 また、EndCommit を呼び出して、トランザクションが実際にコミットされたかどうかを判断することもできます。 なんらかの理由でトランザクションがコミットされなかった場合、EndCommit はトランザクションの例外を発生させます。 EndCommit が呼び出された時点でトランザクションがまだコミットされていない場合、トランザクションがコミットまたは中止されるまで、呼び出し元がブロックされます。
非同期のコミットを行う最も簡単な方法は、コミットの完了時に呼び出されるコールバック メソッドを提供することです。 ただし、呼び出しを実行するために使用する元の EndCommit オブジェクトに対して CommittableTransaction メソッドを呼び出す必要があります。 そのオブジェクトを取得するには、コールバック メソッドの IAsyncResult パラメーターをダウンキャストできます。これは、CommittableTransaction クラスで IAsyncResult クラスが実装されているためです。
次の例は、非同期コミットの実行方法を示しています。
public void DoTransactionalWork()
{
Transaction oldAmbient = Transaction.Current;
CommittableTransaction committableTransaction = new CommittableTransaction();
Transaction.Current = committableTransaction;
try
{
/* Perform transactional work here */
// No errors - commit transaction asynchronously
committableTransaction.BeginCommit(OnCommitted,null);
}
finally
{
//Restore the ambient transaction
Transaction.Current = oldAmbient;
}
}
void OnCommitted(IAsyncResult asyncResult)
{
CommittableTransaction committableTransaction;
committableTransaction = asyncResult as CommittableTransaction;
Debug.Assert(committableTransaction != null);
try
{
using(committableTransaction)
{
committableTransaction.EndCommit(asyncResult);
}
}
catch(TransactionException e)
{
//Handle the failure to commit
}
}
関連項目
.NET