やりたかったこと
Entity Framework CoreでAzureのSQL Databaseに接続し、
- 行の更新は競合させない
- 接続エラーは数回リトライする
を実現したかった
挫折した点
行ロックは相変わらずできない
EF Coreになり、行ロックに対応していないかと期待しましたが、まだ未対応。
System.ComponentModel.DataAnnotations.ConcurrencyCheckを使って楽観ロックをすることで、更新を競合させないことは可能。
https://docs.microsoft.com/ef/core/modeling/concurrency
ConcurrencyCheckをいれたときのリトライがいまいち
公式のドキュメントでは、DbUpdateConcurrencyExceptionをキャッチして、問題のデータをアップデートするような例が提示されています。
ところが、現在のDBの値に応じて、保存したい値が異なる場合、次の(1)と(2)で同じような処理を入れる必要があります。
扱うエンティティも異なるので共通化しづらいし、リトライ機構も自分で実装する必要があり、どうもいまいち。
using (var context = new PersonContext())
{
// データを取得
// (1) 保存したいデータと現在のDBのデータを比較して保存する値を決める
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Person)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// (2) ここで(1)と同じ操作をしなくてはならない
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
参考
https://docs.microsoft.com/ef/core/saving/concurrency
SqlServerRetryingExecutionStrategyはSqlExceptionしか扱わないらしい
Microsoft.EntityFrameworkCore.SqlServerRetryingExecutionStrategyのラッパークラスを作って、DbUpdateConcurrencyExceptionもSELECTからやり直すようにできればシンプルになると考え、作ってみました。
ただ、DbUpdateConcurrencyExceptionが発生しても、そもそもShouldRetryOnに入ってこないようで、挫折
まとめ
Entity Framework Coreになっても、まだいまいちなので他の方法を探したほうがよいな、という印象でした。