本文以 Northwind 資料庫 Customers、Orders 表為例,實戰演示 LINQ to SQL 的新增、查詢操作,並深入解析 **Identity Cache (身分識別快取)** 與 **延遲載入** 兩大核心機制對開發行為的影響。
Northwind.dbmlCustomers、Orders 拖曳至設計介面NorthwindDataContext、Customer、Order 實體類別
var ctx = new NorthwindDataContext();
// 新增客戶
var c = new Customer { CustomerID = "Test1", CompanyName = "Test Company1" };
ctx.Customers.InsertOnSubmit(c);
ctx.SubmitChanges(); // 發送 INSERT 陳述式
// 查詢剛新增的客戶
var c1 = ctx.Customers.Single(c => c.CustomerID == "Test1");
Console.WriteLine($"{c1.CustomerID} {c1.CompanyName}");
// 輸出: Test1 Test Company1
var ctx = new NorthwindDataContext();
var c = new Customer { CustomerID = "Test2", CompanyName = "Test Company2" };
ctx.Customers.InsertOnSubmit(c);
ctx.SubmitChanges(); // 執行 INSERT
// 同一 DataContext 再次查詢主鍵
var c1 = ctx.Customers.Single(ct => ct.CustomerID == "Test2");
Console.WriteLine($"{c1.CustomerID} {c1.CompanyName}"); // 正確輸出
SubmitChanges() 後,DataContext 將新實體放入 **Identity Cache** (內部 Dictionary<object, object>,Key 為主鍵值)。Single(pk) 查詢時,DataContext 偵測到:查詢條件 **僅包含主鍵** 且 **目標型別為實體型別** → 直接從快取回傳實體參考,**完全略過資料庫查詢**。Where(c => c.CompanyName == "X"))ctx.ObjectTrackingEnabled = falsectx.Refresh(RefreshMode.OverwriteCurrentValues, entity) 強制重新載入
var c = ctx.Customers.Single(ct => ct.CompanyName == "Test Company1");
// 此時尚未載入 Orders
foreach (var order in c.Orders) // 存取導航屬性 Orders 時
{
Console.WriteLine(order); // 才發送 SELECT * FROM Orders WHERE CustomerID = 'Test1'
}
Customer 時:**無 Orders 關聯查詢**c.Orders 時:**才發送 Orders 查詢**這與 Hibernate lazy="true" 機制相同:關聯集合預設延遲載入,首次存取才觸發 SQL。
針對大型欄位 (圖片 varbinary(max)、長文本 nvarchar(max)),可在 DBML 設計器屬性視窗將欄位 Delay Loaded 設為 True:

| 機制 | 觸發條件 | 效果 | 控制方式 |
|---|---|---|---|
| Identity Cache | 同一 DataContext + 主鍵查詢 | 回傳快取實體,零 SQL | ObjectTrackingEnabled、Refresh() |
| 延遲載入 (關聯) | 存取導航屬性 (EntitySet/EntityRef) | 按需載入關聯資料 | DBML 關聯屬性、DataLoadOptions.LoadWith 預先載入 |
| Delay Loaded (欄位) | 存取標記為 Delay Loaded 的屬性 | 按需載入大型欄位 | DBML 欄位屬性 Delay Loaded = True |
理解這三大機制,能精準預測 LINQ to SQL 的 SQL 產生行為,避免 N+1 查詢問題、記憶體暴漲與非預期的資料庫往返,寫出高效能資料存取程式碼。