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

LINQ to SQL 更新操作深度解析:Identity Cache 行為與並行衝突三大解決策略

     編輯:關於.NET

本文接續《查詢與新增》,聚焦 LINQ to SQL 更新操作的核心機制:**Identity Cache 導致的查詢回傳已修改值**、以及 **樂觀並行衝突** 的三種解決策略與代碼實戰。

基本更新流程


var ctx = new NorthwindDataContext();
var alfki = ctx.Customers.Single(c => c.CustomerID == "ALFKI");
Console.WriteLine($"Before: {alfki.CompanyName}"); // Alfreds Futterkiste

alfki.CompanyName = "New Company Name";
ctx.SubmitChanges(); // 發送 UPDATE 陳述式

var newAlfki = ctx.Customers.Single(c => c.CustomerID == "ALFKI");
Console.WriteLine($"After: {newAlfki.CompanyName}"); // New Company Name

Identity Cache 實測:SubmitChanges 前查詢即見新值


var ctx = new NorthwindDataContext();
var alfki = ctx.Customers.Single(c => c.CustomerID == "ALFKI");
Console.WriteLine($"Before update: {alfki.CompanyName}"); // Alfreds Futterkiste

alfki.CompanyName = "New Company Name";
// 尚未呼叫 SubmitChanges()

var again = ctx.Customers.Single(c => c.CustomerID == "ALFKI");
Console.WriteLine($"Before submit: {again.CompanyName}"); // New Company Name —— 已見新值!

SQL Profiler 驗證

  • 第二次 Single **無 SELECT 陳述式** 產生
  • DataContext 直接從 Identity Cache 回傳 **同一實體參考** (已在記憶體中修改)

機制本質

  • DataContext 以 **主鍵為 Key** 維護實體快取 (Dictionary)。
  • 實體屬性修改 **即時反映在快取物件上**,無需等待 SubmitChanges。
  • 同一 DataContext 內,主鍵查詢 **永遠回傳同一實體實例**,保證物件圖一致性。

關閉追蹤以繞過快取


ctx.ObjectTrackingEnabled = false; // 關閉 Identity Cache 與變更追蹤
var fromDb = ctx.Customers.Single(c => c.CustomerID == "ALFKI"); // 強制查詢資料庫
⚠️ 關閉追蹤後實體變更不會被自動偵測,SubmitChanges 不會持久化任何更新。

樂觀並行衝突:發生條件與三大解決策略

衝突重現步驟

  1. 應用程式 A 讀取實體 (載入原始值快照)
  2. 資料庫管理工具 (SSMS) 直接修改該記錄
  3. 應用程式 A 修改實體並呼叫 SubmitChanges()
  4. LINQ to SQL 偵測到資料庫值 ≠ 原始值快照 → 拋出 ChangeConflictException

策略一:Column 層級 UpdateCheck 控制


[Column(Storage="_CompanyName", DbType="NVarChar(40) NOT NULL",
        CanBeNull=false, UpdateCheck=UpdateCheck.Never)] // Never / Always / WhenChanged
public string CompanyName { get; set; }
UpdateCheck 值行為適用場景
Always每次 UPDATE 皆檢查該欄位 (WHERE 子句包含該欄位)關鍵業務欄位,嚴格防衝突
Never完全不檢查,**最後寫入者勝**非關鍵欄位、高併發允許覆蓋
WhenChanged僅當實體該屬性被修改時檢查平衡效能與衝突偵測 (預設)

策略二:全域衝突解決模式 (RefreshMode)


try
{
    ctx.SubmitChanges(ConflictMode.FailOnFirstConflict); // 或 ContinueOnConflict
}
catch (ChangeConflictException)
{
    foreach (var conflict in ctx.ChangeConflicts)
    {
        conflict.Resolve(RefreshMode.KeepCurrentValues); // 三種模式擇一
    }
    ctx.SubmitChanges(); // 重試
}
RefreshMode行為語意
KeepCurrentValues**強制寫入** 目前實體所有值 (覆蓋資料庫)「我方優先」/ 最後寫入者勝
KeepChanges合併:已修改屬性寫入新值,未修改屬性同步資料庫最新值「合併優先」/ 保留雙方變更
OverwriteCurrentValues**放棄修改**,重新載入資料庫最新值覆蓋實體「資料庫優先」/ 取消本次更新

策略三:針對單一衝突物件精細解決


catch (ChangeConflictException)
{
    foreach (var conflict in ctx.ChangeConflicts)
    {
        var entity = conflict.Object as Customer;
        if (entity != null && entity.CustomerID == "VIP001")
        {
            conflict.Resolve(RefreshMode.KeepCurrentValues); // VIP 強制寫入
        }
        else
        {
            conflict.Resolve(RefreshMode.OverwriteCurrentValues); // 一般用戶取消
        }
    }
    ctx.SubmitChanges();
}

衝突模式選擇決策樹

  1. **業務允許覆蓋** (如使用者設定、非金流) → UpdateCheck=Never 或 KeepCurrentValues
  2. **業務嚴禁覆蓋** (如庫存扣款、帳戶餘額) → UpdateCheck=Always + OverwriteCurrentValues (取消並通知用戶重試)
  3. **部分欄位可合併** (如訂單備註、標籤) → WhenChanged + KeepChanges

總結

LINQ to SQL 更新核心在於:**Identity Cache 讓同一 DataContext 內「所見即所得」**,**樂觀並行透過原始值快照偵測衝突**。掌握 UpdateCheck (欄位層級)、RefreshMode (衝突解決層級)、ConflictMode (提交行為) 三大控制點,即可精準應對從低併發簡單更新到高併發金融級衝突處理的完整譜系。

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