LINQ to SQL / Entities 查詢首次執行需經「運算式樹 → 邏輯查詢計畫 → 存儲查詢(如 T-SQL)」轉換,成本極高。預編譯可快取存儲查詢,跳過後續轉換。然而許多開發者發現預編譯「無效」,主因在於委托實例隨上下文生命週期釋放,導致每次重新編譯。本文系統講解正確用法。
Entity Framework 使用 System.Data.Objects.CompiledQuery,LINQ to SQL 用 System.Data.Linq.CompiledQuery,核心相同:
// C# 靜態欄位保持委托存活
private static Func<SalesEntities, int, Customer> _custByID;
_custByID = CompiledQuery.Compile<SalesEntities, int, Customer>(
(ctx, id) => ctx.Customers.Where(c => c.ContactID == id).Single());
// 呼叫
Customer cust = _custByID.Invoke(context, custID);
泛型參數依序為:Context 類型、參數類型...、回傳類型。Lambda 參數第一個必須是 Context。
Web 應用、WCF 服務、Repository 模式中,Context 通常生命週期極短(每請求一個)。解法:靜態欄位保存委托,首次使用時編譯:
public static Customer GetCustomer(int ID)
{
if (_custByID == null)
{
_custByID = CompiledQuery.Compile<SalesEntities, int, Customer>(
(ctx, id) => ctx.Customers.Where(c => c.CustomerID == id).Single());
}
return _custByID.Invoke(_context, ID);
}
委托為 static,進程存活期間常駐記憶體,後續呼叫直接複用存儲查詢。
select new { c.Name, c.Email } 產生匿名型別,無法在委托簽名中指定回傳類型。解法:定義具名 DTO 類別(EF 必須用 class,L2S 可用 struct):
public class CustomerSummary { public string Name; public string Email; }
// 預編譯指定具名型別
var q = CompiledQuery.Compile<Ctx, int, CustomerSummary>(
(ctx, id) => ctx.Customers.Where(c=>c.Id==id)
.Select(c => new CustomerSummary { Name=c.Name, Email=c.Email })
.Single());
L2S 預取透過 DataLoadOptions 設定於 Context,非查詢本身。模式:
static DataLoadOptions Load_Customer_Orders_Details = null;
static Func<DataClasses1DataContext, Customer> _CustWithOrders = null;
public Customer GetRandomCustomerWithOrders()
{
if (Load_Customer_Orders_Details == null)
{
Load_Customer_Orders_Details = new DataLoadOptions();
Load_Customer_Orders_Details.LoadWith<Customer>(c => c.Orders);
Load_Customer_Orders_Details.LoadWith<Order>(o => o.Details);
}
if (_context.LoadOptions == null) _context.LoadOptions = Load_Customer_Orders_Details;
if (_CustWithOrders == null)
_CustWithOrders = CompiledQuery.Compile<DataClasses1DataContext, Customer>(
ctx => ctx.Customers.Where(c => c.Orders.Any()).FirstOrDefault());
return _CustWithOrders.Invoke(_context);
}
DataLoadOptions 同樣靜態化,確保複用。
務必監控實際 SQL 產出,確認預編譯生效。
預編譯是 LINQ 效能首要優化,但必須配合靜態委托快取、具名投影型別、靜態 LoadOptions 三大模式,才能在短生命週期 Context 架構下真正受益。下載範例:MSDN Code Gallery。