見出し画像

[Entity Framework] トランザクションのスコープ制御(EF6:Model/Database First)

Entity Framework のコンテキストにおいて、トランザクションは、既定では SaveChanges() を実行したときに暗黙的に使用されます。要件によっては、トランザクションのスコープを明示的に制御したいケースも出てくるでしょう。

EF6 ではトランザクション操作のために DbContext.Database.BeginTransaction/UseTransaction メソッドが導入され、TransactionScope よりも推奨されるようになりました。(Microsoft Docs 解説
ここでは、トランザクションの明示的なスコープ制御を EF6 の Model/Database First で行う例を示します。

※EF4.1 以降で TransactionScope クラスや DbTransaction.BeginTransaction メソッドを使用する例については、「トランザクションのスコープ制御(EF4.1~:Model/Database First)」をご覧ください。
※Code First での制御については「トランザクションのスコープ制御(EF6:Code First)」をご覧ください。

複数回の SaveChanges をまたぐトランザクション

Database.BeginTransaction でトランザクションを開始し、その中で SaveChanges した変更をまとめて Commit します。

// コンテキスト
using (var context = new NorthwindEntities())
{
   // トランザクション開始
   using (var transaction = context.Database.BeginTransaction())
   {
       // 1つめの SaveChanges()
       var product = await context.Products.SingleAsync(p => p.ProductID == 1).ConfigureAwait(false);
       product.ProductName = "New Product Name";
       await context.SaveChangesAsync().ConfigureAwait(false);
       
       // 2つめの SaveChanges()
       var employee = await context.Employees.SingleAsync(e => e.EmployeeID == 1).ConfigureAwait(false);
       employee.Title = "New Title";
       await context.SaveChangesAsync().ConfigureAwait(false);
       
       // まとめてコミット
       transaction.Commit();
   }
}

複数のコンテキストをまたぐトランザクション

あらかじめ接続を開いておいて BeginTransaction メソッドでトランザクションを開始し、その中で複数のコンテキストを操作、SaveChanges した後にまとめて Commit します。

Database/Model First では、コンテキストのコンストラクタには EntityConnection を渡す必要があります。
コンテキストを Dispose しても接続が破棄されないよう、contextOwnsConnection 引数には false を指定します。

トランザクションは Database.UseTransaction メソッドでコンテキストに渡して共用します。

// 接続準備
var workspace = NorthwindEntities.GetMetadataWorkspace();
using (var entityConnection1 = new EntityConnection("name=NorthwindEntities"))
using (var sqlConnection = entityConnection1.StoreConnection)
using (var entityConnection2 = new EntityConnection(workspace, sqlConnection, false))
{
   // あらかじめ接続を開いておく。
   sqlConnection.Open();
   
   // トランザクション開始
   using (var transaction = sqlConnection.BeginTransaction())
   {
       // 1つ目のコンテキストを操作する。
       using (var context = new NorthwindEntities(entityConnection1, false))
       {
           context.Database.UseTransaction(transaction);
           
           var product = await context.Products.SingleAsync(p => p.ProductID == 1).ConfigureAwait(false);
           product.ProductName = "New Product Name";
           await context.SaveChangesAsync().ConfigureAwait(false);
       }
       
       // 別の EntityConnection を使って2つ目のコンテキストを操作する。
       // ※同じ EntityConnection を使用すると InvalidOperationException が発生する。
       using (var context = new NorthwindEntities(entityConnection2, false))
       {
           context.Database.UseTransaction(transaction);
           
           var employee = await context.Employees.SingleAsync(e => e.EmployeeID == 1).ConfigureAwait(false);
           employee.Title = "New Title";
           await context.SaveChangesAsync().ConfigureAwait(false);
       }
       
       // まとめてコミット
       transaction.Commit();
   }
}

// コンテキストの部分クラス
public partial class NorthwindEntities : DbContext
{
   /// <summary>
   /// コンストラクタ。
   /// </summary>
   /// <param name="existingConnection">コンテキストで使用する接続。</param>
   /// <param name="contextOwnsConnection">false を指定すると、コンテキストが Dispose されたときに接続を Dispose しない。</param>
   public NorthwindEntities(DbConnection existingConnection, bool contextOwnsConnection)
       : base(existingConnection, contextOwnsConnection)
   {
   }
   
   /// <summary>
   /// メタデータワークスペースを取得する。
   /// </summary>
   /// <returns></returns>
   public static MetadataWorkspace GetMetadataWorkspace()
   {
       using (var context = new NorthwindEntities())
       {
           var objectContext = ((IObjectContextAdapter)context).ObjectContext;
           return objectContext.MetadataWorkspace;
       }
   }
}

それぞれのコンテキストには別々の EntityConnection を渡す必要があります。
2つ目以降の EntityConnection を作成するときに必要となる MetadataWorkspace オブジェクトは、コンテキストが実装する IObjectContextAdapter インターフェイスの ObjectContext プロパティから取得できます。

この記事が気に入ったらサポートをしてみませんか?