トランザクション スコープ

TransactionScope クラスを使用すると、トランザクションに参加するコード ブロックを簡単な方法で指定できます。トランザクション スコープでは、関連するトランザクションが自動的に選択されて管理されます。new ステートメントで TransactionScope をインスタンス化すると、参加するトランザクションがトランザクション マネージャーによって判別されます。トランザクション スコープ内 (つまり、TransactionScope オブジェクトの初期化から Dispose メソッドの呼び出しまで) で例外が発生しなかった場合、そのスコープの参加トランザクションが続行されます。トランザクション スコープ内で例外が発生した場合、参加トランザクションがロールバックされます。トランザクションとして実行される処理がすべて完了すると、Complete メソッドが一度だけ呼び出されます。これにより、そのトランザクションがコミット可能であることがトランザクション マネージャーに通知されます。Complete メソッドが呼び出されなかった場合、トランザクションは終了します。トランザクション スコープの詳細については、MSDN のドキュメントを参照してください。

.NET Framework でのトランザクション スコープの実装

System.Transactions 名前空間は、Microsoft .NET Framework Version 2.0、3.0、3.5、および 4 に含まれています。この名前空間は、ADO.NET と SQL Server CLR (共通言語ランタイム) の統合に完全に融合される新しいトランザクション フレームワークを提供します。System.Transactions.transactionscope クラスは、接続を分散トランザクションに暗黙的に参加させることで、コード ブロックをトランザクション対応にします。TransactionScope で指定されたコード ブロックの最後には、Complete メソッドが呼び出されます。Dispose メソッドが呼び出される前に Complete メソッドが呼び出されなければ、トランザクションは中止されます。例外がスローされた場合、トランザクションは中止されたと見なされます。

詳細については、https://msdn2.microsoft.com/en-us/library/ms172070(VS.80).aspx を参照してください。

System.Transactions の制限

.NET Compact Framework 2.0 では、System.Transactions 名前空間はサポートされません。したがって、実装は Windows デスクトップ オペレーティング システムに限定されます。また、.NET Framework 2.0、.NET Framework 3.0、.NET Framework 3.5、または .NET Framework 4 が必要となります。

タイムアウトが発生した場合、System.Transactions インフラストラクチャによって、別のスレッドから Rollback が呼び出されます。別のスレッドで発生しているロールバックは、メイン スレッドからは見えません。時間のかかるトランザクションでは、非決定的な動作や部分的なコミットが発生する場合があります。この問題を解決するには、トランザクション スコープ オブジェクトの作成時に、オブジェクトのタイムスパンを増やします。

また、トランザクション スコープに参加している既存のトランザクション マネージャーが存在しなければ、トランザクション スコープには、SqlCeConnection オブジェクトを 1 つだけ参加させることができます。

トランザクション スコープの外側で開いた接続を、既存のトランザクション スコープに参加させる必要がある場合は、EnlistTransaction メソッドを使用します。

TransactionScope の実装

SQL Server Compact は、リソースとして System.Transactions インフラストラクチャに参加します。

既定では、トランザクション スコープに参加している接続で複数のコマンドを開いた場合、これらのコマンドは現在のトランザクション コンテキストに参加することになります。トランザクション スコープに参加していない接続を開くこともできます。こうすることで、参加しないコマンドを作成できます。既定では、トランザクション スコープでの SQL Server Compact トランザクションは、シリアル化可能なトランザクション タイプになります。

SQL Server Compact では分散トランザクションがサポートされないため、同一のトランザクション スコープや、同一の関連トランザクション スコープを共有する入れ子になったトランザクション スコープに、複数の接続を参加させることはできません。

トランザクション スコープに参加している接続で、明示的なトランザクションは使用できません。

接続を暗黙的に参加させることはできません。トランザクション スコープに参加させるためには、次の手順に従います。

  • トランザクション スコープ内で接続を開きます。

  • または、接続が既に開いている場合は、その接続オブジェクトで EnlistTransaction メソッドを呼び出します。

SQL Server Compact の制限

トランザクション スコープに関連した SQL Server Compact と SQL Server の相違点は、次のとおりです。

  • SQL Server Compact では分散トランザクションはサポートされていません。そのため、ローカル トランザクションを、自動的に完全な分散トランザクションに昇格させることはできません。

  • SQL Server Compact では、複数のアクティブな結果セットに対しても並列トランザクションがサポートされます。これに対し、SQL Server では並列トランザクションはサポートされません。

TransactionScope の例 1

次の例では、TransactionScope を使用して、接続をトランザクションに参加させた後、そのトランザクションをコミットしています。

using (TransactionScope transScope = new TransactionScope())

{

using (SqlCeConnection connection1 = new

SqlCeConnection(connectString1))

{

/* Opening connection1 automatically enlists it in the

TransactionScope as a lightweight transaction. */

connection1.Open();

// Do work in the connection.

}

// The Complete method commits the transaction.

transScope.Complete();

}

TransactionScope の例 2

次の例では、TransactionScope を使用してデータベース内に 2 つのテーブルを作成しています。

static void Setup(String strDbPath)

{

/* Delete the database file if it already exists. We will create a new one. */

if (File.Exists(strDbPath))

{

File.Delete(strDbPath);

}

// Create a new database.

SqlCeEngine engine = new SqlCeEngine();

engine.LocalConnectionString = @"Data source = " + strDbPath;

engine.CreateDatabase();

engine.Dispose();

}

/* This function creates two tables in the specified database. Before creating the tables, it re-creates the database.

These tables are created in a TransactionScope, which means that either both of them will be created or not created at all. */

static void CreateTablesInTransaction(String strDbPath)

{

/* Create the connection string. In order to have the connection enlisted into the TransactionScope, the Enlist property in the connection string must be explicitly set to true. */

String strConn = @"Data source = " + strDbPath + ";Enlist=true";

SqlCeConnection conn = new SqlCeConnection(strConn);

try

{

Setup(strDbPath); // Create a new database for our tables.

using (TransactionScope scope = new TransactionScope())

{

/* To enlist a connection into a TransactinScope, specify 'Enlist=true' in its connection string and open the connection in the scope of that TransactionScope object. */

conn.Open();

// Create the tables.

SqlCeCommand command = conn.CreateCommand();

command.CommandText = @"create table t1(col1 int)";

command.ExecuteNonQuery();

command.CommandText = @"create table t2(col1 int)";

command.ExecuteNonQuery();

/* If this statement is executed and the TransactionScope has not timed out, t1 and t2 will be created in the specified database. */

scope.Complete();

}

}

catch (SqlCeException e)

{

Console.WriteLine(e.Message);

}

finally

{

if (conn.State != System.Data.ConnectionState.Closed)

{

conn.Close();

}

conn.Dispose();

}

}

TransactionScope の例 3

次の例では、TransactionScope 内で 2 つのテーブルに値を挿入しています。

/* This function assumes that tables t1(col1 int) and t2(col1 int) are already created in the specified database. The condition for the following function is this:

If INSERTs into the first table succeed, then INSERT into the second table. However, if the INSERTs into the second table fail, roll back the inserts in the second table but do not roll back the inserts in the first table. Although this can also be done by way of regular transactions, this function demonstrates how to do it using TransactionScope objects. */

static void CreateTableAndInsertValues(String strDbPath)

{

/* Create the connection string. To have the connection enlisted into the TransactionScope, the Enlist property in the connection string must be explicitly set to true. */

String strConn = @"Data source = " + strDbPath + ";Enlist=true";

SqlCeConnection conn1 = new SqlCeConnection(strConn);

SqlCeConnection conn2 = new SqlCeConnection(strConn);

try

{

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))

{

conn1.Open();

SqlCeCommand command1 = conn1.CreateCommand();

command1.CommandText = @"insert into t1(col1) values(1)";

command1.ExecuteNonQuery();

command1.CommandText = @"insert into t1(col1) values(2)";

command1.ExecuteNonQuery();

/* If this statement is executed and the TransactionScope has not timed out, two records will be inserted into table 1. */

scope.Complete();

try

{

using (TransactionScope scopeInner = new TransactionScope(TransactionScopeOption.RequiresNew))

{

conn2.Open();

SqlCeCommand command2 = conn2.CreateCommand();

command2.CommandText = @"insert into t2(col1) values(1)";

command2.ExecuteNonQuery();

command2.CommandText = @"insert into t2(col1) values(2)";

command2.ExecuteNonQuery();

/* If this statement is run and the TransactionScope has not timed out, two records will be inserted into table 2. */

scopeInner.Complete();

}

}

catch (SqlCeException e)

{

Console.WriteLine(@"Exception in Inner block: " + e.Message);

}

}

}

catch (SqlCeException e)

{

Console.WriteLine(@"Exception in Outer block: " + e.Message);

}

finally

{

// Close both the connections.

if (conn1.State != System.Data.ConnectionState.Closed)

{

conn1.Close();

}

if (conn2.State != System.Data.ConnectionState.Closed)

{

conn2.Close();

}

conn1.Dispose();

conn2.Dispose();

}

}