承接《查詢與新增》、《更新》兩文,本文完整剖析 LINQ to SQL 刪除操作的核心行為:基本刪除、Identity Cache 導致的「刪除後仍可查詢」現象、關聯實體級聯刪除邏輯差異、以及並行衝突處理。
var ctx = new NorthwindDataContext();
var test1 = ctx.Customers.Single(c => c.CustomerID == "TEST1");
ctx.Customers.DeleteOnSubmit(test1);
ctx.SubmitChanges(); // 發送 DELETE 陳述式
資料庫中該筆記錄確實被移除。
var ctx = new NorthwindDataContext();
var test1 = ctx.Customers.Single(c => c.CustomerID == "TEST1");
ctx.Customers.DeleteOnSubmit(test1);
ctx.SubmitChanges(); // DB 已刪除
// 同一 DataContext 再次查詢
var again = ctx.Customers.Single(c => c.CustomerID == "TEST1");
Console.WriteLine(again.CustomerID); // 輸出 "TEST1" —— 似乎沒刪除!
DataContext **Identity Cache 仍保留實體參考**DataContext 查詢 → 找不到資料 (拋出 InvalidOperationException Single 失敗)entity.GetType() 是否為代理類別、或檢查 ctx.GetChangeSet().Deletes 確認刪除狀態⚠️ 實務中若刪除後需立即驗證,**請使用新 DataContext 或關閉 ObjectTrackingEnabled**。
場景:Customer (1) —< Order (*),欲刪除某客戶特定訂單。
var order1 = test1.Orders.Single(o => o.ShipCity == "Shanghai");
test1.Orders.Remove(order1); // 僅移除關聯
ctx.SubmitChanges();
SQL Profiler 顯示:發送 UPDATE Orders SET CustomerID = NULL WHERE OrderID = ... —— **只是斷開關聯,未真正刪除 Order 記錄!**
var order1 = test1.Orders.Single(o => o.ShipCity == "Shanghai");
test1.Orders.Remove(order1); // 維護物件圖一致性
ctx.Orders.DeleteOnSubmit(order1); // 真正標記為刪除
ctx.SubmitChanges(); // 發送 DELETE FROM Orders WHERE OrderID = ...
選項 A:應用層手動級聯 (不依賴資料庫 FK Cascade)
var ctx = new NorthwindDataContext();
var test1 = ctx.Customers.Single(c => c.CustomerID == "TEST1");
foreach (var o in test1.Orders.ToList()) // ToList 避免迭代中修改集合
{
test1.Orders.Remove(o);
ctx.Orders.DeleteOnSubmit(o);
}
ctx.Customers.DeleteOnSubmit(test1);
ctx.SubmitChanges();
選項 B:資料庫層級 ON DELETE CASCADE (推薦)
-- SQL Server 設定外鍵級聯刪除
ALTER TABLE Orders
ADD CONSTRAINT FK_Orders_Customers
FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
ON DELETE CASCADE;
ctx.Customers.DeleteOnSubmit(customer); ctx.SubmitChanges();customer.Orders 集合不會自動清空,後續存取可能引發例外,建議刪除後捨棄該 DataContext。刪除時若資料已被他人修改/刪除,SubmitChanges() 拋出 ChangeConflictException。處理模式:
try { ctx.SubmitChanges(); }
catch (ChangeConflictException)
{
foreach (var conflict in ctx.ChangeConflicts)
{
// KeepCurrentValues: 強制刪除 (忽略並行衝突)
// OverwriteCurrentValues: 放棄刪除 (重新載入資料庫值)
conflict.Resolve(RefreshMode.KeepCurrentValues);
}
ctx.SubmitChanges(); // 重試
}
刪除操作通常選KeepCurrentValues(強制刪除) 或OverwriteCurrentValues(放棄刪除),視業務語意決定。
DeleteOnSubmit + SubmitChangesParent.Children.Remove(child) + ctx.Children.DeleteOnSubmit(child) (雙重呼叫)ON DELETE CASCADE,應用層僅刪父實體ctx.ExecuteCommand("DELETE ...") (參考《大量刪除效能優化》)DataContext 或關閉追蹤掌握 Remove (維護物件圖) 與 DeleteOnSubmit (標記資料庫刪除) 的職責分離,是正確使用 LINQ to SQL 刪除操作的關鍵。