LINQ to SQL 在斷開環境(N-Tier)下更新實體,Attach 方法充滿陷阱。本文從最簡單場景層層遞進,複現所有異常,並通過反編譯源碼揭示根因,為後續方案鋪墊。
var db = new NorthwindDataContext();
var p = db.Products.Single(x => x.ID == 1);
p.Name = "Changed";
db.SubmitChanges(); // 成功
// Get 隔離上下文
public Product GetProduct(int id) {
using (var db = new NorthwindDataContext())
return db.Products.SingleOrDefault(p => p.ID == id);
}
// Update 不同上下文
public void UpdateProduct(Product p) {
using (var db = new NorthwindDataContext()) {
db.Products.Attach(p); // 默認 Attach(entity, false)
db.SubmitChanges();
}
}
原因:實體含關聯屬性(Category、Supplier 等),觸發延遲加載器,HasDeferredLoaders=true,StandardChangeTracker.Track 拋出異常。
LINQ to SQL 預設樂觀並發檢查需 RowVersion(timestamp 列)或 IsVersion=true。無版本成員時,Attach(entity, true) 被禁止。
即使主表有 Timestamp,關聯實體(Category)無 Timestamp 仍觸發異常 1。
依然異常 1,因關聯實體延遲加載器未斷開。
if (trackedObject.HasDeferredLoaders)
throw Error.CannotAttachAddNonNewEntities();
HasDeferredLoaders 遍歷所有 Association 與 Deferred 成員,檢查是否有延遲加載器。只要實體存在任何關聯導航屬性,Attach 即失敗。
if (asModified && (versionMember == null) && inheritanceType.HasUpdateCheck)
throw Error.CannotAttachAsModifiedWithoutOriginalState();
HasUpdateCheck 檢查是否有 UpdateCheck != Never 的列。預設所有列 Always,故必須有版本列。
僅 第 7 情況:有 RowVersion 且無關聯 時 Attach(entity, true) 正常工作。這在實際系統中幾乎不可能。
LINQ to SQL 設計初衷側重連線場景(UI 直連),斷開更新並非一等公民。Repository 模式需自行解決 Attach 限制,下篇將給出方案。