TransactionScope の使用は、トランザクションで複数の SQL コマンドを実行する最も簡単な方法です。使用方法の例を次に示します:
using System.Transactions;
using (var trxScope = new TransactionScope())
{
movieRepo.Insert(newMovie);
movieRepo.Delete(movieToDelete);
//Commits the transaction
trxScope.Complete();
};
//Rolls back the transaction if Complete() wasn't called
Code language: C# (cs)
TransactionScope.Complete() を呼び出すと、トランザクションがコミットされます。 Complete() を呼び出さない場合、TransactionScope ブロックを離れるとトランザクションがロールバックされます。
これにより、コードがきれいに保たれ、Repository パターンとうまく連携します。以下は、リポジトリ クラスの Insert()/Delete() メソッドです。トランザクションをまったく処理する必要がないことに注意してください。
using Dapper;
using System.Data.SqlClient;
public class MovieRepository
{
public void Insert(Movie movie)
{
using (var con = new SqlConnection(connectionString))
{
con.Execute(INSERT_SQL, param: movie);
}
}
public void Delete(Movie movie)
{
using (var con = new SqlConnection(connectionString))
{
con.Execute(DELETE_SQL,
param: new { id = movie.Id });
}
}
//rest of class
}
Code language: C# (cs)
注:SQL Server 2016 データベースに対して .NET 5 を使用しています。
TransactionScope ブロック内で開かれた接続はすべて、自動的にトランザクションに登録されます。
分散トランザクション
トランザクションが作成されると、ローカル トランザクションとして開始されます。特定の条件下では、分散トランザクション コーディネーター (MSDTC) を必要とする分散トランザクションにエスカレートされます。 実行するサービス。トランザクションがエスカレートする主な条件が 2 つあります。
- 同時にトランザクション スコープで 2 つの接続を明示的に開く
- 別の接続文字列を使用する (別のサーバーに接続している場合など)
使用しているデータベース エンジン/バージョンも役割を果たします。分散トランザクションを処理する必要があるかどうかは、開発プロセスの早い段階で判断することをお勧めします。これは、それらがアーキテクチャ上の障害になる可能性があるためです。理想的には、分散トランザクションを避けるようにしてください。
.NET Core でサポートされていない分散トランザクション
分散トランザクションは現在、.NET のクロスプラットフォーム バージョン (.NET Core 以降) ではサポートされていません。 Microsoft が最終的にサポートを追加する可能性があります。トランザクションのエスカレーションをトリガーする何かを行うと、次の例外が発生します:
.NET Core に移行していて、分散トランザクションが必要な場合、これは分散トランザクションの必要性を排除するために再設計を必要とする主要なブロッカーです。
注:MSDTC サービスが実行されていない場合、「MSDTC を利用できません」というエラーが表示されることがありますが、これは無関係であるため混乱を招きます。 MSDTC が実行されている場合、「プラットフォームがサポートされていません」という例外が発生します。
.NET Framework での分散トランザクション
分散トランザクションは .NET Framework でサポートされており、分散トランザクション コーディネーター (MSDTC) が必要です。 実行するサービス。トランザクションがエスカレートされ、MSDTC サービスが実行されていない場合、エラーが発生します:
MSDTC サービスが実行中で、自動的に開始するように設定されていることを確認してください。
同じサーバー上の異なるデータベースに接続する際のトランザクションのエスカレーションの回避
同じサーバー上の異なるデータベースに接続している場合でも、異なる接続文字列がトランザクションのエスカレーションをトリガーします。たとえば、次のコード (TransactionScope 内で実行) はトランザクションのエスカレーションをトリガーし、「プラットフォームがサポートされていません」という例外で失敗します (クロスプラットフォーム .NET の場合):
public void Insert(Movie movie)
{
using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbNew;Integrated Security=true"))
{
con.Execute(INSERT_SQL, param: movie);
}
}
public void Delete(Movie movie)
{
using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbOld;Integrated Security=true"))
{
con.Execute(DELETE_SQL,
param: new { id = movie.Id });
}
Code language: C# (cs)
トランザクションのエスカレーションを回避する 1 つの方法は、同じ接続文字列を使用し、USE
public void Insert(Movie movie)
{
using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbNew;Integrated Security=true"))
{
con.Execute(INSERT_SQL, param: movie);
}
}
public void Delete(Movie movie)
{
using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbNew;Integrated Security=true"))
{
con.Execute("USE MoviesDbOld");
con.Execute(DELETE_SQL,
param: new { id = movie.Id });
}
}
Code language: C# (cs)
これは同じ接続文字列であるため、トランザクションをエスカレートしません。
注:これは、con.Open() + con.ChangeDatabase(“MoviesDbOld”) を呼び出すのと同じですが、Dapper に接続を開かせる方が簡単なだけです。
TransactionScope の代替 – Connection.BeginTransaction()
トランザクションをより明示的に制御したい場合は、TransactionScope を使用する代わりに Connection.BeginTransaction() スタイルを使用できます。以下に例を示します:
using Dapper;
using System.Data.SqlClient;
using(var con = new SqlConnection(connectionString))
{
con.Open();
using(var trx= con.BeginTransaction())
{
con.Execute(INSERT_SQL, param: movieToInsert, transaction: trx);
con.Execute(DELETE_SQL, param: new { movieToDelete.Id }, transaction: trx);
trx.Commit();
}
}
Code language: C# (cs)
注意すべきことの 1 つは、BeginTransaction() を呼び出す前に接続を開く必要があることです。
Commit() を呼び出さない場合は、ブロックを使用して BeginTransaction を離れるときに、トランザクションが自動的にロールバックされます。 Rollback() を自分で呼び出すこともできます。