見出し画像

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

Entity Framework のコンテキストにおいて、トランザクションは、既定では SaveChanges() を実行したときに暗黙的に使用されます。

要件によっては、トランザクションのスコープを明示的に制御したいケースも出てくるでしょう。
ここではトランザクションの明示的なスコープ制御を EF6 の Code First で行う方法をご紹介します。

※Entity Framework Core でのトランザクション制御については Microsoft Docs で詳しく解説されています。
※Model/Database First の場合は「トランザクションのスコープ制御(EF4.1~)」「トランザクションのスコープ制御(EF6)」をご覧ください。

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

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

// コンテキスト
using (var context = new NorthwindContext())
{
   // トランザクション開始
   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 します。

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

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

// 接続準備
using (var sqlConnection = new SqlConnection(NorthwindContext.GetConnectionString()))
{
   // あらかじめ接続を開いておく。
   sqlConnection.Open();
   
   // トランザクション開始
   using (var transaction = sqlConnection.BeginTransaction())
   {
       // 1つ目のコンテキストで保存
       using (var context = new NorthwindContext(sqlConnection, 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);
       }
       
       // 2つ目のコンテキストで保存
       using (var context = new NorthwindContext(sqlConnection, 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 class NorthwindContext : DbContext
{
   :
   
   /// <summary>
   /// コンストラクタ。
   /// </summary>
   /// <param name="existingConnection">コンテキストで使用する接続。</param>
   /// <param name="contextOwnsConnection">false を指定すると、コンテキストが Dispose されたときに接続を Dispose しない。</param>
   public NorthwindContext(DbConnection existingConnection, bool contextOwnsConnection)
       : base(existingConnection, contextOwnsConnection)
   {
   }
   
   /// <summary>
   /// 接続文字列を取得する。
   /// </summary>
   /// <returns></returns>
   public static string GetConnectionString()
   {
       using (var context = new NorthwindContext())
       {
           return context.Database.Connection.ConnectionString;
       }
   }
   
   :
}

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