首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

資料點:LINQ 查詢預編譯完全指南 — 避開效能陷阱

     編輯:關於.NET

前言:預編譯的必要性與常見誤區

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());

陷阱二:LINQ to SQL 預取與預編譯結合

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 Server Profiler
  • Hibernating Rhinos 工具:L2SProf、EFProf

務必監控實際 SQL 產出,確認預編譯生效。

總結

預編譯是 LINQ 效能首要優化,但必須配合靜態委托快取、具名投影型別、靜態 LoadOptions 三大模式,才能在短生命週期 Context 架構下真正受益。下載範例:MSDN Code Gallery。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved