接上篇「問題重重」,本文詳細剖析五種解決 Attach 異常的方案原理、代碼實現與優缺點,為性能測試奠基。
查詢原實體,逐屬性覆蓋新值。本質:繞過 Attach,利用上下文跟蹤原實體變更。
var old = db.Products.Single(p => p.ID == new.ID);
old.Name = new.Name; old.Price = new.Price; ...
db.SubmitChanges();
db.ObjectTrackingEnabled = false 同時關閉 DeferredLoadingEnabled,查詢返回無延遲加載器的實體,Attach(entity, true) 成功。
public Product GetProduct(int id)
{
var db = new NorthwindDataContext();
db.ObjectTrackingEnabled = false;
var loads = new DataLoadOptions();
loads.LoadWith<Product>(p => p.Category);
loads.LoadWith<Product>(p => p.Supplier);
db.LoadOptions = loads;
return db.Products.SingleOrDefault(p => p.ProductID == id);
}
Attach/SubmitChanges;需顯式 LoadWith 載入關聯。手動或反射將實體的 EntityRef 置 null、EntitySet 清空,斷開延遲加載器,使 HasDeferredLoaders 為 false。
// 反射通用 Detach
foreach (var fi in entity.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fi.FieldType.Name.Contains("EntityRef")) fi.SetValue(entity, null);
if (fi.FieldType.Name.Contains("EntitySet"))
{
var val = fi.GetValue(entity);
val?.GetType().GetMethod("Clear")?.Invoke(val, null);
}
}
將查詢謂詞與更新動作封裝在同一 DataContext 實例中執行,完全繞開 Attach。
public void UpdateProduct(Expression<Func<Product,bool>> predicate, Action<Product> action)
{
using (var db = new NorthwindDataContext())
{
var p = db.Products.SingleOrDefault(predicate);
action(p);
db.SubmitChanges();
}
}
// 調用
repo.UpdateProduct(p => p.ID == 1, p => { p.Name = "New"; p.Price = 10; });
拼接 SQL,僅更新髒列。屬框架級方案(如 Ezsocio RepositoryBase.UpdateEntity),此處不展開。
各方案各有取捨,下篇將通過性能測試量化差異,給出選型指南。