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