本文接續《查詢與新增》,聚焦 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
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 —— 已見新值!
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 不會持久化任何更新。
SubmitChanges()ChangeConflictException
[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 | 僅當實體該屬性被修改時檢查 | 平衡效能與衝突偵測 (預設) |
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();
}
UpdateCheck=Never 或 KeepCurrentValuesUpdateCheck=Always + OverwriteCurrentValues (取消並通知用戶重試)WhenChanged + KeepChangesLINQ to SQL 更新核心在於:**Identity Cache 讓同一 DataContext 內「所見即所得」**,**樂觀並行透過原始值快照偵測衝突**。掌握 UpdateCheck (欄位層級)、RefreshMode (衝突解決層級)、ConflictMode (提交行為) 三大控制點,即可精準應對從低併發簡單更新到高併發金融級衝突處理的完整譜系。