[Entity Framework] トランザクションのスコープ制御（EF6：Code First）

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

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

※Entity Framework Core でのトランザクション制御については [Microsoft Docs](https://docs.microsoft.com/en-us/ef/core/saving/transactions) で詳しく解説されています。
※Model/Database First の場合は「[トランザクションのスコープ制御（EF4.1～）](https://qiita.com/CodeOne/items/ea2ed5a37aa0537eaeef)」「[トランザクションのスコープ制御（EF6）](https://qiita.com/CodeOne/items/d45bf7a69ed8533857f2)」をご覧ください。

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

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

```c#
// コンテキスト
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](https://qiita.com/CodeOne/items/d45bf7a69ed8533857f2#%E8%A4%87%E6%95%B0%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E3%81%BE%E3%81%9F%E3%81%90%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3) と異なり、コンテキストのコンストラクタに渡す接続は `EntityConnection` を介しません。
コンテキストを `Dispose` しても接続が破棄されないよう、`contextOwnsConnection` 引数には `false` を指定します。

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

```c#
// 接続準備
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;
        }
    }
 
    :
}
```