[Entity Framework] トランザクションのスコープ制御（EF4.1～：Model/Database First）

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

要件によっては、トランザクションのスコープを明示的に制御したいケースも出てくるでしょう。
ここでは EF4.1 以降の DbContext を例に、その方法をご紹介します。

※EF6 では別の方式が推奨されるようになりました。「[トランザクションのスコープ制御（EF6：Model/Database First）](https://qiita.com/CodeOne/items/d45bf7a69ed8533857f2)」をご参照ください。
※Code First 版は[こちらの記事](https://qiita.com/CodeOne/items/cde232063bcc795b89f4)で公開しています。

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

```c#
// コンテキスト
using (var context = new NorthwindEntities())
{
    // SaveChanges() を実行するたびに接続が開閉され、分散トランザクションになるのを防ぐため、あらかじめ開いておく。
    ((IObjectContextAdapter)context).ObjectContext.Connection.Open();

    // TransactionScope で囲む。
    var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
    using (var scope = new TransactionScope(TransactionScopeOption.Required, options))
    {
        // 1つめの SaveChanges()
        var product = context.Products.Single(p => p.ProductID == 1);
        product.ProductName = "New Product Name";
        context.SaveChanges();

        // 2つめの SaveChanges()
        var employee = context.Employees.Single(e => e.EmployeeID == 1);
        employee.Title = "New Title";
        context.SaveChanges();

        // まとめてコミット
        scope.Complete();
    }
}
```

TransactionScope 内で例外が発生した場合は、トランザクションがロールバックされます。

※TransactionScope を使用するには、System.Transactions.dll を参照設定する必要があります。

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

既存の接続をコンストラクタに指定してコンテキストを生成することによって、複数のコンテキスト間で接続やトランザクションを共用することができます。

```c#
// 共用する接続の作成
using (var connection = new EntityConnection("name=NorthwindEntities"))
{
    // SaveChanges() を実行するたびに接続が開閉され、分散トランザクションになるのを防ぐため、あらかじめ開いておく。
    connection.Open();

    // TransactionScope で囲む。
    var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
    using (var scope = new TransactionScope(TransactionScopeOption.Required, options))
    {
        // 1つめのコンテキスト（接続を指定して生成）
        using (var context = new NorthwindEntities(connection, false))
        {
            var product = context.Products.Single(p => p.ProductID == 1);
            product.ProductName = "New Product Name";
            context.SaveChanges();
        }

        // 2つめのコンテキスト（接続を指定して生成）
        using (var context = new NorthwindEntities(connection, false))
        {
            var employee = context.Employees.Single(e => e.EmployeeID == 1);
            employee.Title = "New Title";
            context.SaveChanges();
        }

        // まとめてコミット
        scope.Complete();
    }
}

// コンテキストの部分クラス
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)
    {
    }
}
```

## トランザクション直接操作

TransactionScope を使用せずに、DbTransaction オブジェクトを直接操作することも可能です。

```c#
using (var context = new NorthwindEntities())
{
    var connection = ((IObjectContextAdapter)context).ObjectContext.Connection;
    connection.Open();

    // トランザクション開始
    using (var transaction = connection.BeginTransaction())
    {
        try
        {
            var product = context.Products.Single(p => p.ProductID == 1);
            product.ProductName = "New Product Name";
            context.SaveChanges();

            var employee = context.Employees.Single(e => e.EmployeeID == 1);
            employee.Title = "New Title";
            context.SaveChanges();

            // コミット
            transaction.Commit();
        }
        catch
        {
            // ロールバック
            transaction.Rollback();
            throw;
        }
    }
}
```