在 N 層架構或 Web 開發中,資料查詢與更新往往不在同一個 DataContext 作用域內。LINQ to SQL 提供 Table<T>.Attach 方法將脫離上下文的實體重新關聯,但直接使用常拋出異常:
System.NotSupportedException: An attempt has been made to Attach or Add an entity that is not new...
本文針對 .NET Framework 3.5 版本,以 Northwind 資料庫為例,深入剖析異常成因並提供最佳實踐方案。
在 Table<T>.Attach(TEntity entity, bool asModified) 中,滿足任一條件即導致失敗:
DataContext.ObjectTrackingEnabled = false(唯讀上下文)SubmitChanges()DataContext 已被 Dispose()asModified=true 但實體屬性含 UpdateCheck=WhenChanged/Always 且無原始值記錄Attach 內部呼叫 StandardChangeTracker.Track(entity, true),第二個參數 true 表示遞迴檢查所有巢狀關聯。在 TrackedObject.HasDeferredLoaders 屬性中:
internal override bool HasDeferredLoaders {
get {
foreach (MetaAssociation assoc in this.Type.Associations) {
if (this.HasDeferredLoader(assoc.ThisMember)) return true;
}
// ...檢查延遲載入成員...
return false;
}
}
關鍵在 HasDeferredLoader:
private bool HasDeferredLoader(MetaDataMember deferredMember) {
if (!deferredMember.IsDeferred) return false;
MetaAccessor storageAccessor = deferredMember.StorageAccessor;
if (storageAccessor.HasAssignedValue(this.current) || storageAccessor.HasLoadedValue(this.current))
return false;
IEnumerable boxedValue = (IEnumerable)deferredMember.DeferredSourceAccessor.GetBoxedValue(this.current);
return (boxedValue != null); // 延遲來源不為空卻未載入 → 拋出異常
}
核心邏輯:若關聯屬性(EntityRef/EntitySet)未被賦值或載入,且其延遲載入來源(DeferredSource)不為 null,表示該關聯綁定已銷毀的 DataContext,無法再執行延遲載入,故拒絕附加。
嘗試預先載入 Customer.Orders 仍失敗,因 Order 物件本身還有未載入的關聯。必須移除所有關聯定義才能成功——這在實務中不可行。
Customer customer = null;
using (var context = CreateNorthwnd()) {
var option = new DataLoadOptions();
context.DeferredLoadingEnabled = false; // 關閉延遲載入
option.LoadWith<Customer>(c => c.Orders); // 明確指定需載入的關聯
context.LoadOptions = option;
customer = context.Customers.First();
}
customer.City = "Beijing";
using (var context = CreateNorthwnd()) {
context.Customers.Attach(customer); // 附加實體
context.Refresh(RefreshMode.KeepCurrentValues, customer); // 取得原始值進行並發檢測
context.SubmitChanges();
}
此模式優勢:
Attach(entity, false) + Refresh(KeepCurrentValues) + SubmitChanges() 避免重複鍵衝突using 區塊管理 DataContext,操作完成即釋放,避免記憶體洩漏。ObjectTrackingEnabled=false 提升效能,搭配 LoadWith/AssociateWith 精準控制載入範圍。DeferredLoadingEnabled=false),麻煩大於好處。Attach(false) + Refresh(KeepCurrentValues) + SubmitChanges() 模式。本文配套原始碼可供下載驗證。