1つのトランザクションで同じエンティティを更新する?
入荷データを更新しながら一気に在庫データを更新するようなシチュエーションで、
var list = <なんらかのソース> using (MyEntity entity = new MyEntity()){ foreach (var source in list){ Arrival arrival = new Arrival(){ count = source.count , : (データ転送など) }; entity.arrivals.Add(arrival); //商品コードで検索とか Stock stock = entity.stocks.FirstOrDefault(st=>st.itemId == source.itemId); if (stock==null){ stock = new Stock(){itemId = source.itemId,}; entity.stocks.Add(stock); } stock.count += count ; } entity.SaveChanges(); }
こんな感じのコードはよくあるんだけど、source.itemIdが重複してるとエラーになる。
これはシンプルにFirstOrDefault()が毎回データソースを参照してて、それ以前に行われたAdd()による追加分が参照されないことによる。
Entityに対する変更はDbSet
//商品コードで検索とか // 先にLocalを検索する Stock stock = entity.stocks.Local.FirstOrDefault(st=>st.itemId == source.itemId); // まだLocalになければデータソースを検索する if (stock==null) stock = entity.stocks.FirstOrDefault(st=>st.itemId == source.itemId);
検索するソース違いで同じラムダを書くのは馬鹿らしいのでもうちょっと展開する。
Func<Stock,bool> searchCondition = (st)=> st.itemId == source.itemId; //商品コードで検索とか // 先にLocalを検索する Stock stock = entity.stocks.Local.FirstOrDefault(searchCondition); // まだLocalになければデータソースを検索する if (stock==null) stock = entity.stocks.FirstOrDefault(searchCondition);
うーん。美しくない。
そもそも
- DbSet
と、DbSet .Localを検索する - Localになければデータを検索する
ってのは共通なんだから呼び出し側じゃなくてDbSet側にメソッドがあるべき。
ということでLinqの拡張メソッドで
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data.Entity; using System.Data.Entity.Infrastructure; namespace MyNamespace{ public static class MyEFExtension { public static T FirstOrDefaultCached<T >(this DbSet<T> source, Func<T, bool> predicate) where T:class { T res = source.Local.FirstOrDefault(predicate); if (res == null) res = source.FirstOrDefault(predicate); return res; } } }
これを用意することで
Stock stock = entity.stocks.FirstOrDefaultCached(st=> st.itemId==source.itemId);
と書ける。
つーか
キャッシュ込みで検索できる仕組みって用意されてないんだろか…。