首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

深入解析 LINQ to SQL Table<T>.Attach 異常與最佳實踐

     編輯:關於.NET

在 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 資料庫為例,深入剖析異常成因並提供最佳實踐方案。

Attach 失敗的六大條件

在 Table<T>.Attach(TEntity entity, bool asModified) 中,滿足任一條件即導致失敗:

  1. DataContext.ObjectTrackingEnabled = false(唯讀上下文)
  2. 正在執行 SubmitChanges()
  3. DataContext 已被 Dispose()
  4. asModified=true 但實體屬性含 UpdateCheck=WhenChanged/Always 且無原始值記錄
  5. 嘗試附加已屬於當前上下文的物件
  6. 實體包含未載入的 Association 屬性或巢狀 Association(核心原因)

深入原始碼追蹤:為何第 6 條會拋出異常?

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 異常
  • 避免 N 層架構中延遲載入導致的連線錯誤
  • Attach(entity, false) + Refresh(KeepCurrentValues) + SubmitChanges() 避免重複鍵衝突

LINQ to SQL 應用心得總結

  1. 建議 using 區塊管理 DataContext,操作完成即釋放,避免記憶體洩漏。
  2. 純查詢場景設 ObjectTrackingEnabled=false 提升效能,搭配 LoadWith/AssociateWith 精準控制載入範圍。
  3. N 層應用強烈建議關閉延遲載入(DeferredLoadingEnabled=false),麻煩大於好處。
  4. 更新斷離實體採用 Attach(false) + Refresh(KeepCurrentValues) + SubmitChanges() 模式。

本文配套原始碼可供下載驗證。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved