LINQ to SQL 採用樂觀並發機制處理資料衝突。提交更新時比對資料庫目前值與查詢時原始值,相同則提交,不同則拋出 ChangeConflictException 待開發者決定處理策略。
執行 UPDATE/DELETE 時,WHERE 子句包含主鍵 + 所有欄位原始值:
UPDATE [Customers] SET [CompanyName] = @p10
WHERE ([CustomerID] = @p0) AND ([CompanyName] = @p1) ... -- 參數為原始值
-- @@ROWCOUNT = 0 表示資料已被他人修改 → 拋出 ChangeConflictException
可透過 ColumnAttribute.UpdateCheck、IsVersion、IsPrimaryKey 控制檢測欄位,減少 WHERE 條件長度。
var customerA = context.Customers.First();
var customerB = context.Customers.Where(c => c.CustomerID == customerA.CustomerID).Single();
customerA == customerB; // true,身分識別快取返回同一實例
customerA.CompanyName = "A";
customerB.CompanyName = "B";
context.SubmitChanges(); // 不衝突,最終值為 "B"
var contextA = GenerateContext();
var contextB = GenerateContext(); // 模擬另一用戶
var customerA = contextA.Customers.First();
customerA.CompanyName = "From A";
var customerB = contextB.Customers.Where(c => c.CustomerID == customerA.CustomerID).Single();
customerB.CompanyName = "From B";
contextB.SubmitChanges(); // 先提交
try {
contextA.SubmitChanges(); // 拋出 ChangeConflictException
} catch (ChangeConflictException) {
// 處理衝突
}
DataContext.ChangeConflicts.ResolveAll(RefreshMode) 三種模式:
KeepCurrentValues:保留目前值,重新讀取資料庫值作為新原始值OverwriteCurrentValues:以資料庫值覆寫目前值KeepChanges:嘗試合併變更(僅衝突欄位以資料庫值覆寫)SubmitChanges() 自動建立交易,任一操作失敗整體回滾。
| 模式 | 行為 | 適用場景 |
|---|---|---|
FailOnFirstConflict(預設) | 遇衝突即停止並回滾 | 需交易完整性(如銷售+庫存) |
ContinueOnConflict | 記錄所有衝突繼續執行,最後拋出彙總異常 | 批次日誌寫入等無強一致性需求 |
// ADO.NET 風格
context.Transaction = context.Connection.BeginTransaction();
try { context.SubmitChanges(); context.Transaction.Commit(); }
catch { context.Transaction.Rollback(); }
// TransactionScope 風格(推薦)
using (var scope = new TransactionScope()) {
context.SubmitChanges();
scope.Complete();
}