Edited at

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

EF6 では TransactionScope より DbContext.Database.BeginTransaction/UseTransaction メソッドが推奨されるようになりました。

トランザクションのスコープの明示的な制御をEF6の Model/Database First で行う例を以下に示します。

※EF4.1~5 の場合は「トランザクションのスコープ制御(EF4.1~:Model/Database First)」をご覧ください。

※Code First 版も公開予定です。


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

// コンテキスト

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();
}
}


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

// 接続準備

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;
}
}
}