Entity Framework Coreで行ロックを取得する方法
Entity Framework Core(以下EF Core)でデータ取得と同時に更新ロック(行ロック)をかける方法を紹介します。
SQLで記述した場合、以下のようなクエリに対応する処理です。
-- SQL Server
SELECT * FROM [TABLE_NAME] WITH (UPDLOCK)
-- Oracle
SELECT * FROM [TABLE_NAME] FOR UPDATE
以下の環境で動作確認を行っています。
- .NET Core 3.1 (C# 8.0)
- Microsoft SQL Server 2016 Express Edition
- Entity Framework Core 5.0.4
更新ロックを取得する方法
調べた限りではEF Coreは標準で更新ロックを取得する仕組みを提供していないようです。
その為、生SQLクエリを使用する方法で対応します。
// dbSet は DbContext と紐づけられた DbSet<T> のインスタンスです
dbSet.FromSqlRaw<T>($"SELECT * FROM [{テーブル名}] WITH (UPDLOCK)")
FromSqlRaw
メソッドはIQueryable<T>
を返すため、続けてWhere
メソッド等で対象を絞り込むクエリが実現できます。
繰り返し使用することになると思いますので、共通メソッドを定義すると良いと思われます(以下の例は拡張メソッドとして記述しています)。
その際、クエリを作成するためにテーブル名が必要となりますので、何らかの方法で取得する必要があります。
テーブル名がエンティティ型のクラス名と一致する場合は以下のように書けます(T
はエンティティ型とします)。
public static IQueryable<T> ForUpdate<T>(this DbSet<T> dbSet) where T : class
{
return dbSet.FromSqlRaw<T>($"SELECT * FROM [{typeof(T).Name}] WITH (UPDLOCK)");
}
クラス名がテーブル名と異なる場合は、エンティティクラスにTable
属性1でテーブル名を対応づけ、これを参照するのが簡単です。2
public static IQueryable<T> ForUpdate<T>(this DbSet<T> dbSet) where T : class
{
var table = (TableAttribute)Attribute.GetCustomAttribute(typeof(T), typeof(TableAttribute));
return dbSet.FromSqlRaw<T>($"SELECT * FROM [{table.Name}] WITH (UPDLOCK)");
}
更新ロックの必要性について
通常、EF CoreはUPDATE等の更新クエリはSaveChanges
メソッドを実行した時点で発行される為、ロックを取得する時間が最小限となります。
// context は DbContext のインスタンスです
using var transaction = context.Database.BeginTransaction();
try
{
var entities = context.Table.ToList();
// この時点ではロックは取得しない
// データ編集等
if (context.SaveChanges() > 0)
{
// SaveChangesメソッドで更新系クエリが発行され、更新ロックが取得され、
// Commitを行うまでの間ロックが保持される
transaction.Commit();
}
}
catch
{
transaction.Rollback();
}
ロックの競合を防ぐ観点ではこのままでも良いのですが、データ編集のコストが高い場合にデータの取得から実際に更新を行うまでの間隔が長くなってしまい、他者の更新等が割り込まれてデータの不整合を引き起こす可能性が高まります。
そういった場合ではデータを取得した時点で更新ロックを取得し、データ編集処理中のDB変更を抑制する必要があります。
-
Table
属性:System.ComponentModel.DataAnnotations.Schema.TableAttribute
↩ -
エンティティクラスに
Table
属性を付与する方法は以下を参照ください。
https://docs.microsoft.com/ja-jp/ef/core/modeling/entity-types?tabs=data-annotations ↩