本文章是由機器翻譯。

資料點

先行編譯 LINQ 查詢

Julie Lerman

下載程式碼範例

在應用程式中使用 LINQ to SQL 或LINQ to Entities 它 ’s 重要考慮先行編譯您建立並重複執行任何查詢。 我經常取得攔截在完成特定工作,並運用先行編譯的查詢,直到 ’m 相當遠沿著開發過程中忽略了。 這是很像是 「 例外處理 disease 」 devs 嘗試 shoehorn 事實之後的例外處理到應用程式。

不過,甚至您實作這個重要的效能增強技術之後 ’s 失去其好處的好機會。 您可能會注意到 promised 的效能增益 isn’t 正在實現,但原因 (和修正程式) 可能逃離您。

在本專欄中我先說明如何先行編譯查詢,然後專注於遺失先行編譯的優點,在 Web 應用程式、 服務與其他案例的問題。 您學習到如何確保您跨越回傳、 短暫的服務作業和其他超出範圍中將要到重要的執行個體的程式碼取得效能優勢。

先行編譯查詢

轉換成相關的儲存區查詢 (比方說,T-SQL 由資料庫執行) 的 LINQ 查詢的程序是相當昂貴內的查詢執行較大的傘。 圖 1 顯示牽涉到一個 LINQ to Entities查詢轉換為儲存區查詢的程序。


圖 1 轉換成相關的儲存區查詢的 LINQ 查詢

Entity Framework 團隊 ’s 部落格張貼 「 探索 ADO.NET Entity Framework-第 1 部分效能 」 ( blogs.msdn.com/adonet/archive/2008/02/04/exploring-the-performance-of-the-ado-net-entity-framework-part-1.aspx ),關閉處理程序會中斷,並顯示相對的每個步驟所花費的時間。 請注意這個張貼根據 Microsoft.NET Framework 3.5 SP1 版本的 Entity Framework 時間每個步驟散發有可能是移位新版本中。 不過,先行編譯是仍然昂貴一部份查詢執行程序。

藉由先行編譯您的查詢,Entity Framework 與 LINQ to SQL 可以重複使用儲存區查詢,並略過的找出每次重複的程序。 比方說如果您的應用程式經常會從資料存放區擷取不同客戶,您可能有這類的查詢:

Context.Customers.Where(c=>c.CustomerID==_custID)

當執行任何動作但 _custID 參數變成從一個執行下一個時,為什麼浪費努力調換這給 SQL 指令一再重複?

LINQ to SQL 和 Entity Framework 兩者都啟用查詢先行編譯 ; 不過,因為的處理序中兩個架構之間有些差異,它們各自取得自己 CompiledQuery 類別。 LINQ to SQL 使用 System.Data.LINQ.CompiledQuery 而 Entity Framework 使用 System.Data。 Objects.CompiledQuery。 兩種形式的 CompiledQuery 允許您傳入參數,而且兩者都需要您傳遞目前 DataContext 或 ObjectContext 正在使用中。 基本上,編碼觀點它們 ’re 相等。

CompiledQuery.Compile 方法會傳回可以的函式的形式委派依序被叫用在需要。

以下是簡單的查詢,這是靜態,因此 doesn’t 需要執行個體化 Entity Framework ’s CompiledQuery 類別所編譯:

var _custByID = CompiledQuery.Compile<SalesEntities, int, Customer>
    ((ctx, id) =>ctx.Customers.Where(c=> c.ContactID == id).Single());
Dim _custByID= CompiledQuery.Compile(Of SalesEntities, Integer, Customer) 
    (Function(ctx As ObjectContext, id As Integer) 
    ctx.Customers.Where(Function(c) c.CustomerID = custID).Single)

您可以在查詢運算式中使用 LINQ 方法或 LINQ 運算子。 這些查詢是用 LINQ 方法和 lambdas 所建置而來。

語法是比典型泛型方法更令人困惑,所以我細分它。 再次,編譯方法的目標是要建立 Func (委派) 可以叫用,稍後如下所示:

CompiledQuery.Compile<SalesEntities, int, Customer>
CompiledQuery.Compile(Of SalesEntities, Integer, Customer)

因為它 ’s 泛型,必須何種型別會在做為引數,傳遞會告知方法,以及叫用 (Invoke) 委派時,將會傳回何種類型。 最少式,您必須 ObjectContext 或 DataContext 某些類型為傳入 LINQ to SQL。 您可以指定一個 System.Data.Objects.ObjectContext 或從它衍生的項目。 在我的情況下,我明確使用衍生的類別與我的Entity Data Model相關聯的 SalesEntities。

您也可以定義必須放在直接在內容之後的多個引數。 在我的範例我告訴編譯產生的先行編譯的查詢也應該接受 int/整數參數。 最後一個型別將告訴您什麼查詢將會被傳回,在我的情況下 Customer 物件:

((ctx, id) =>ctx.Customers.Where(c => c.ContactID == id).Single())
Function(ctx As ObjectContext, id As Integer) 
    ctx.Customers.Where(Function(c) c.CustomerID = custID).Single

先前的編譯方法執行的結果會是下列的委派:

private System.Func<SalesEntities, int, Customer> _custByID

Private _custByID As System.Func(Of SalesEntities, Integer, Customer)

一旦已編譯查詢,您叫只是用它每當您要執行該查詢傳入 ObjectContext 或 DataContext 執行個體和所需任何其他參數。 此處我有具名 _commonContext 和一個名為 _custID 的變數執行個體:

Customer cust  = _custByID.Invoke(_commonContext, _custID);

第一次叫用委派查詢轉譯成存放區查詢該轉譯快取處理及供重複使用上叫用的後續呼叫處理。 LINQ 可以略過的編譯查詢工作,並向右前往執行。

確保真正正在使用先行編譯的查詢

有 ’s 有先行編譯的查詢不-所以-明顯,並不廣泛已知,問題。 許多開發人員都以為查詢在應用程式處理序中快取,且會停。 我當然是對這個假設,因為我發現,否則表示的執行任何動作 — 除了對於某些 unimpressive 效能數字。 但是,您執行個體化已編譯的查詢物件超出領域時, 您也會遺失先行編譯的查詢。 需要將會再次為了先行編譯每次使用讓您完全失去該先行編譯的優點。 在實際上付較高的價格比您只已執行因為到某些額外的精力 CLR 必須進行至委派的 LINQ 查詢時所執行的動作一樣。

Rico Mariani digs 到他部落中使用委派的成本 「 效能測驗 # 13 — LINQ to SQL 編譯查詢成本 — 方案 」 ( blogs.msdn.com/ricom/archive/2008/01/14/performance-quiz-13-linq-to-sql-compiled-query-cost-solution.aspx )。 註解中的討論是同等 enlightening。

我見過的部落格報告關於 LINQ 來實體 ’ 「 可怕 」 中的效能 「 即使有先行編譯的查詢 」 的 Web 應用程式。原因是每次網頁回傳後、 您 ’re 取得一個新的執行個體化之內容和 重新 -先行編譯查詢。 先行編譯的查詢是永遠不會取得重複使用。 您有相同問題的短暫內容內的任何一個地方。 這可能會發生在一個明顯的地方如一個 Web 或 Windows Communication Foundation (WCF)] 服務或甚至在較少的東西明顯如會具現化新的內容,即時如果執行個體上的儲存機制 hasn’t 被提供。

您可以使用靜態 (在 VB 中的共用,) 變數保留查詢跨處理序,並叫用它使用任何內容目前可用來避免委派的遺失。

這裡 ’s 的模式,我順利搭配 Web 應用程式 WCF 服務而存放庫,其中 [ObjectContext 超出範圍經常與我想要可以使用整個應用程式程序委派。 您需要宣告靜態委派在其中您呼叫 「 查詢類別的建構函式中。 此處我宣告符合先前建立的已編譯的查詢的委派:

static Func<ObjectContext, int, Customer> _custByID;
Shared _custByID As Func(Of ObjectContext, Integer, Customer)

有幾個可能的地方編譯查詢。 您可以執行它在類別建構函式或只是之前於叫用它。 這裡是一種方法,設計來執行查詢,並傳回 Customer 物件:

public static Customer GetCustomer( int ID)
    {
      //test for an existing instance of the compiled query
      if (_custByID == null)
      {
        _custByID = CompiledQuery.Compile<SalesEntities, int, Customer>
         ((ctx, id) => ctx.Customers.Where(c => c.CustomerID == id).Single());
      }
      return _custByID.Invoke(_context, ID);
    }

此方法會使用已編譯的查詢。 先它會編譯上即時但只查詢必要時,可決定藉由測試來查看是否查詢已經被尚未產生。 如果您正在編譯在類別建構函式中,需要執行相同的測試,以確定您只編譯在必要時使用資源。

因為委派 _custByID,靜態所以它會保留在記憶體其包含類別超出範圍時。 因此,只要應用程式的程序本身是在範圍中,委派則可以使用 ; won’t 是 null,且編譯步驟將會被略過。

先行編譯的查詢及預測

還有要注意的一些其他速度碰撞會更容易找到。 第一個 revolves 周圍估算,但 isn’t 特有的不自覺地重新編譯先行編譯的查詢 
problem。 當您的專案中某個查詢,而不是傳回特定類型的資料行時,永遠因此取得匿名型別。

在定義查詢時指定它的傳回型別不可能的因為有 ’s 沒有方法可以說出 「 匿名型別類型 」。如果您希望自己能夠將結果傳回的方法內的查詢因為 can’t 指定此方法會傳回什麼,有相同的問題。 使用 LINQ 開發人員經常擊中這個後者的限制。

如果您專注於匿名型別是在即時型別 isn’t 是用來重複使用的事實,這些限制時失望,意義。 匿名型別 aren’t 是用來傳遞周圍從方法給方法。

為何您 ‘ ll 需要做為您先行編譯的查詢是定義型別符合該投影。 請注意 Entity Framework 中您必須使用類別不是結構如 LINQ to Entities won’t 允許您專案轉為 doesn’t 具有建構函式的型別。 LINQ to SQL 並允許結構作為目標的投射。 所以,Entity Framework 為您只能使用一個類別但 LINQ to SQL,您可以使用類別或結構以避免周圍的匿名型別的限制。

先行編譯的查詢與 LINQ to SQL Prefetching

先行編譯查詢與另一個潛在的問題牽涉到 prefetching,或 立即載入 ,但問題只以 SQL 發生與 LINQ。 Entity Framework 在您可以使用包含方法,以產生單一查詢在資料庫中執行的立即載入。 因為包含可以如 context.Customer.Include(“Orders”),查詢的一部份不 ’s 發生的問題。 不過,與 LINQ to SQL,立即載入被定義在的 DataContext 不查詢本身。

DataContext.LoadOptions 具有 LoadWith 方法,可讓您指定哪些相關的資料應該取得 eager 與特定實體一起載入。

您可以定義 LoadWith 用於載入具有查詢時任何客戶的訂單:

Context.LoadOptions.LoadWith<Customer>(c => c.Orders)

然後您就可以將一個規則,讓它寫著 [載入就會載入任何訂單的詳細資訊:

Context.LoadOptions.LoadWith<Customer>(c => c.Orders)
Context.LoadOptions.LoadWith<Order>(o =>o.Details)

您可以定義 [LoadOptions 直接針對 DataContext 執行個體或建立 DataLoadOptions 類別、 在這個物件中定義 LoadWith 規則並再將它附加至您的內容:

DataLoadOptions _myLoadOptions = new DataLoadOptions();
_myLoadOptions.LoadWith<Customer>(c => c.Orders)
Context.LoadOptions= myLoadOptions

有 LoadOptions 和 DataLoadOptions 類別的一般使用的警告。 比方說如果您定義,並對 [DataContext 執行查詢之後,然後附加 DataLoadOptions,can’t 附加一組新的 DataLoadOptions。 有 ’s 更多,您可以深入了解各種載入選項,以及他們的警告,但 let’s 看一下的先行編譯的查詢中套用某些 LoadOptions 基本模式。

圖樣關鍵在於您可以預先在 DataLoadOptions 定義不含特定的內容與關聯。

在類別宣告,您可在此宣告靜態函式變數來包含先行編譯的查詢,宣告新的 DataLoadOptions 變數。 ’s 對這個變數設靜態讓它,太,仍可與委派一起重要的:

static DataLoadOptions Load_Customer_Orders_Details = new DataLoadOptions();

然後在 [之編譯,並叫用查詢方法中,您可以定義與委派一起 LoadOptions (請參閱 的 圖 2)。 這個方法是在.NET Framework 3.5 和.NET Framework 4 中有效。

圖 2 定義連同一個委派 LoadOptions

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 都是靜態的它們定義只在必要時。 根據您的類別邏輯,[DataContext 可能,或可能不是新。 如果它 ’s 重複使用的內容,它會具有先前指派的 LoadOptions。 否則您必須將它們指派。 現在您能夠重複地叫用這項查詢,並仍然得到 LINQ to SQL 的好處 prefetching 功能。

在您檢查清單的頂端保持先行編譯

LINQ 查詢範圍中, 查詢編譯會是執行的處理程序昂貴的一部份。 您 LINQ 查詢邏輯加入您 LINQ to SQL 或 Entity Framework 為基礎的應用程式的任何時間您應該考慮先行編譯查詢及重複使用它們。 但 don’t 假設您就 ’re 有已完成。 如您看到有位置您可能不優點從先行編譯查詢的案例。 使用某些類型的程式碼剖析工具,例如 SQL Profiler 或其中程式碼剖析工具從 Hibernating Rhinos,包括 L2SProf ( l2sprof.com/.com ) 和 EFProf ( efprof.com )。 您可能需要運用一些以確保您取得先行編譯查詢承諾的邊緣此處顯示的模式。

Danny Simmons 從 Microsoft Entity Framework] 小組說明如何控制合併選項,先行編譯查詢時 — 並列出要注意的一些其他因素 — 在他的部落格張貼在 blogs.msdn.com/dsimmons/archive/2010/01/12/ef-merge-options-and-compiled-queries.aspx

 

Julie Lerman   是一個 Microsoft MVP 在.NET 輔導員及居住在 Vermont 丘陵的顧問。 您可以找到她資料存取和其他 Microsoft.NET 主題在使用者群組和世界各地的研討會上呈現。 在 thedatafarm.com/blog Lerman 部落格是高度 acclaimed 書籍 「 發展 Entity Framework 」 O’Reilly 媒體,2009年作者。 您可以在 Twitter 上依照她在 Twitter.com/julielermanvt

多虧給來檢閱這份文件的技術專家下列:   Danny Simmons