【技術メモ】Entity Frameworkの保有情報が更新されない
EntityFrameworkに関して、何も考えずにDataTableとかと同じように考えて使用していたら、ドツボにはまった反省です。
似て非なるというのはこういうことなのかな・・・。違うか。
テンプレートに従う
ASP.NET MVCでスキャフォールディングを使用して作成した場合、こんな感じでコードが作成されます。
public class HogeController : Controller { private HogeDbContext db = new HogeDbContext(); public ActionResult Index() { return View(); } }
privateで宣言されたContextは、「まぁ、コネクション管理してるんでしょ」くらいな感覚で認識していました。
その後、検索やら保存やら行う処理を書いて、簡単な検索・更新が行える画面を作成し、同時処理違反の処理を実装する段階で問題が発覚しました。
public IEnumerable<hoge> GetAll() { // 全件取得 return db.hoge.Select(s => s); }
「あれ?他で更新された内容が検索に反映されないゾ・・・???」
きちんと、保存前にもDBに対してSELECTを発行しているにも関わらず、返ってくる結果は古い情報・・・。
これってキャッシュが残っているよね?なんで??
DbContext内のオブジェクトは,Contextを破棄するまで残りつづけます.
んー、やっぱりそういう事か。じゃあ、リロード処理を組み込んでやればいいだけかな。
調べるとEntity Frameworkには「ChangeTracker」という変更追跡を処理するコンテキストプロパティーがあり、こいつが各Entryの「Reload」を投げられそうなので、こんな感じのリロード機能を付け足してみました。
public IEnumerable<hoge> GetAll() { // リロード foreach (var entity in db.ChangeTracker.Entries()) { entity.Reload(); } // 全件取得 return db.hoge.Select(s => s); }
おお、ちゃんとリロードされたぞ。これで良し。
じゃあ、100件処理すると、どうなるのかな?
アカーンっ!!!(宮川大輔 風)
ものの見事に100件分のクエリーが生成され、投げられたようです・・・。
どうも1件ずつクエリーを投げて更新するようで、複数レコードを処理する場合には向かないという事が分かりました。
結局のところ
スキャフォールディングで生成されたContextは使用せずに、必要最小限の範囲で使用するように変更しました。(usingを使用)
変更追跡の部分は、最初に取得した際の情報をAttach関数を使用して登録時に渡して判断するように修正。
絶対に他に良い方法はあるとは思うんですが、今の所はこれが自分の中では最適解なのかな、やっぱり。
vb6でConnectionを自分で管理していた時は、「Openして繋ぎっぱなしなんて悪」という認識だったしなぁ。
Repositoryパターンでのトランザクションの扱いと共に、悩ましい問題です。