こんにちは、弱小エンジニアのいすいです。
今日はEntity Framework Coreで悲観的ロックをする方法を備忘録で残しておきます。
間違いがあったらご教授いただけると幸いです。
やりたいこと
やりたいことしては以下です。
【API仕様】あるAPIの処理でDBのデータから条件Aをクリアしたら、新しくデータを追加できる
【問題】同時アクセスを受けたときに「条件A」を両方クリアし、両方ともデータが追加される
【理想】同時アクセスを受けた時、2番目の方が「条件A」をクリアしていない場合は2番目の方はデータが追加されてほしくない
【解決案】Select〜Insertの間、他のトランザクションのDBへのアクセスをロックしたい
解決案が悲観的ロックのことだと自分は認識しています。
↓参考URL(悲観的ロックとは)
https://qiita.com/NagaokaKenichi/items/73040df85b7bd4e9ecfc
SQL Server(他のDBでも)にはロックの概念(UPLOCKなど)が存在していますが、
insertを制御する機能は見当たらず、そもそもトランザクションを制御するのはDBでは無理なのでは?という結論に至りました。
(DBレベルでのロックは更新ロックが限界だと思われる)
解決方法
トランザクションにはトランザクション分離レベルというものが存在し、
トランザクション中のDBへのアクセスを段階ごとに制御できるらしい。
↓参考URL(トランザクション分離レベル(対象SQL Server)とは)
https://qiita.com/song_ss/items/38e514b05e9dabae3bdb
上記によるとトランザクション分離レベルはデフォルトではREAD COMMITTEDになっているらしく、SERIALIZABLEにすれば、悲観的ロックができそう
それがわかったら、Entity Framework Coreでトランザクション分離レベルを設定しよう。(SQL Serverでもトランザクション分離レベルは設定できそうだが、EF Coreの方が早そう)
↓参考URL(EF Coreでのトランザクション(Microsoft公式)について)
https://docs.microsoft.com/ja-jp/ef/core/saving/transactions
上記によると、EF CoreではTransactionScopeでトランザクション分離レベルを設定できるみたい
なので、悲観的ロックをしたい処理を下記で囲めば良い
1 2 3 4 5 6 7 |
using (var scope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })) { // 処理(select~insert) scope.complete(); } |
実際にinsert処理に該当するSaveChanges()にブレークポイントを置いて、同時アクセスしてみてください。
ちゃんと条件に一致してない2番目の方はエラーで返されると思います。
以上でEF Coreの悲観的ロックが達成できること祈ります。

Thanks for the good article, I hope you continue to work as well.