LoginSignup
7
6

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-02-27

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 プロパティから取得できます。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6