本文章是由機器翻譯。

資料點

分析實體框架中的資料庫活動

Julie Lerman

下載代碼示例

在上個月的“資料點”專欄 (msdn.microsoft.com/magazine/gg309181) 中,我介紹過如何針對 SQL Azure 分析實體框架的性能。 這個月,我將介紹一種不同的分析(即查詢分析),來瞭解應對資料庫執行哪些查詢和命令,以便對實體框架中的查詢及其他資料訪問活動進行回應。

實體框架的一個主要功能是生成命令,用於執行資料庫查詢以及插入、更新和刪除。 這對許多開發人員來說是很有益處的,儘管關於物件關係映射 (ORM) 工具生成的 SQL 與專家手動編寫的 SQL 哪個品質更好的爭論一直存在(在本文中我不會參與這個爭論)。 大多數情況下,生成的 SQL 相當好,尤其考慮到不管您得到的 LINQ to Entities 或實體 SQL 查詢運算式多有創意,都必須用常規方法動態構造 SQL。

儘管已經非常注重改進實體框架 4 中的命令生成,但注意資料庫中發生的情況仍然很重要。 生成的存儲查詢的品質只是事情的一部分。 您編寫的代碼可能會造成資料庫執行時間延長或特別多的資料庫往返次數。 這些是您在分析應用程式時要注意的關鍵點。

在推出實體框架的最初幾年內,除了資料庫分析工具(如 SQL 事件探查器)之外沒有任何其他可用的工具。儘管 SQL 事件探查器提供了很多資訊,但需要通過大量配置和挖掘,才能以一種易於理解的方式查看結果。 以下是除資料庫分析工具之外的用於對實體框架進行查詢分析的各種選項。

實體框架 ObjectContext.ToTraceString 方法

實體框架 API(3.5 和 4)提供用於在運行時檢查查詢的單一方法 ToTraceString,此方法很有用,但只提供有關對資料庫進行的調用子集的資訊。 ToTraceString 是 ObjectQuery 的一種方法,因此如果您要編寫 LINQ to Entities 查詢,則必須先將查詢轉換為 ObjectQuery,然後才能調用 ToTraceString。 例如:

var query = from c in context.Customers where c.CustomerID == 3 select c;
var objectQuery=query as System.Data.Objects.ObjectQuery;
Console.WriteLine(objectQuery.ToTraceString());

這將輸出以下字串:

SELECT
[Extent1].[CustomerID] AS [CustomerID],
[Extent1].[Title] AS [Title],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[MiddleName] AS [MiddleName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Suffix] AS [Suffix],
[Extent1].[CompanyName] AS [CompanyName],
[Extent1].[SalesPerson] AS [SalesPerson],
[Extent1].[EmailAddress] AS [EmailAddress],
[Extent1].[Phone] AS [Phone],
[Extent1].[ModifiedDate] AS [ModifiedDate],
[Extent1].[TimeStamp] AS [TimeStamp]
FROM [SalesLT].[Customer] AS [Extent1]
WHERE 3 = [Extent1].[CustomerID]

請注意,該代碼示例不執行查詢。 ToTraceString 也一樣,它使實體框架在資料庫提供程式 System.Data.SqlClient 的説明下處理查詢(轉換為存儲查詢),但不強制對資料庫執行查詢。

您只能將 ToTraceString 與顯式定義的查詢一起使用。 因此,您不能使用它來查看作為使用 Load 方法的延期載入或延遲載入的結果執行的查詢。 您也不能使用它來檢查插入、更新、刪除或存儲過程執行等活動。

最後,請務必注意,您無法通過創建調試器視覺化工具等方法輕鬆讓 ToTraceString 的結果進入調試過程。 這要求 ObjectQuery 是可序列化的,而它不是。

使用 Visual Studio 2010 IntelliTrace 分析

Visual Studio 2010 Ultimate 中提供了 IntelliTrace,但較低版本中未提供。 IntelliTrace 捕獲資料庫活動(包括由實體框架觸發的活動),但不顯示與命令一起發送的參數值。

接下來是一些執行下列任務的代碼:

  1. 執行 10 個客戶的查詢
  2. 延遲載入第一個返回的客戶的訂單
  3. 禁用延遲載入
  4. 顯式載入第二個返回的客戶的訂單
  5. 修改客戶
  6. 使用 SaveChanges 方法發送對資料庫進行的更改
  7. 執行已映射到實體資料模型中的某存儲過程的函數
var query = from c in context.Customers select c;
var custList = query.Take(10).ToList();
Customer custFirst = custList[0];
int orderCount = custFirst.Orders.Count; 
context.ContextOptions.LazyLoadingEnabled=false;
Customer custSecond = custList[1];
custSecond.Orders.Load(); 
custSecond.ModifiedDate = DateTime.Now;
context.SaveChanges(); 
ObjectResult<PartialOrderDetails> orders= 
  context.GetCustomerOrdersForId(custList[2].CustomerID);

運行時,此代碼將強制執行三條 SELECT 語句、一條 UPDATE 語句,然後為數據庫中的存儲過程執行一條 Execute 命令。

查看圖 1 中的 IntelliTrace 螢幕擷取畫面,您會看到所有這五條命令。

圖 1 顯示在 Visual Studio IntelliTrace 顯示幕中的一系列資料庫命令

但是,展開其中一項(如圖 2 所示)只顯示相關命令而不顯示參數。

圖 2 Visual Studio 2010 IntelliTrace 功能收集的詳細 Select 語句

因此,如果您要查看包括參數在內的資料庫活動,則需要使用某種類型的外部探查器。

MSDN 代碼庫中的 EFTracingProvider

Jarek Kowalski 在 Microsoft 的實體框架團隊時曾編寫過 EFTracingProvider。有一個版本是針對 Microsoft .NET Framework 3.5,還有一個版本是針對 .NET Framework 4。

若要使用 EFTracingProvider,您需要圍繞 ObjectContext 類 AWEntities 構建一個包裝,然後使用此包裝代替 AWEntities。這個擴展類提供跟蹤方法,如您可用來記錄上下文活動的日誌。下麵是 EFTracingProvider 下載包含的必需類包裝的示例。您還將在下載中找到有關本文 (code.msdn.microsoft.com/mag201012DataPoints) 的相關代碼。另外,您還需要在應用程式的設定檔中添加兩個 DbProviderFactories 設置。完成所有以上操作後,您即可產生實體擴展的上下文並開始記錄日誌。

下麵的示例將創建一個文字檔來捕獲日誌事件,然後使用 TracingProvider.Log 方法記錄所有活動:

using (TextWriter logFile = File.CreateText("sqllogfile.txt"))
{
  using (var context = new ExtendedAWEntities())
  {
    context.Log = logFile;
    var query = from c in context.Customers select c;
    var custList = query.Take(10).ToList();
  }
  Console.WriteLine(File.ReadAllText("sqllogfile.txt"));
}

通過使用 TracingProvider 包裝和 ExtendedAWEntities 上下文類,重新運行了與上面的 IntelliTrace 示例相同的一組代碼。

記錄了所有五條資料庫命令,並且每條命令與其相關參數記錄在一起。

作為一個示例,圖 3 顯示作為延遲載入的結果發送的命令,其中 EntityKeyValue1 參數的值在命令列出後指定。

圖 3 EFTracingProvider 捕獲的命令

    SELECT
    [Extent1].[SalesOrderID] AS [SalesOrderID],
    [Extent1].[OrderDate] AS [OrderDate],
    [Extent1].[DueDate] AS [DueDate],
    [Extent1].[OnlineOrderFlag] AS [OnlineOrderFlag],
    [Extent1].[SalesOrderNumber] AS [SalesOrderNumber],
    [Extent1].[PurchaseOrderNumber] AS [PurchaseOrderNumber],
    [Extent1].[AccountNumber] AS [AccountNumber],
    [Extent1].[CustomerID] AS [CustomerID],
    [Extent1].[BillToAddressID] AS [BillToAddressID],
    [Extent1].[CreditCardApprovalCode] AS [CreditCardApprovalCode],
    [Extent1].[SubTotal] AS [SubTotal],
    [Extent1].[Comment] AS [Comment],
    [Extent1].[ModifiedDate] AS [ModifiedDate],
    [Extent1].[ShipDate] AS [ShipDate]
    FROM [SalesLT].[SalesOrderHeader] AS [Extent1]
    WHERE [Extent1].[CustomerID] = @EntityKeyValue1
    -- EntityKeyValue1 (dbtype=Int32, size=0, direction=Input) = 1

EFTracingProvider 易於實現,它向您提供實體框架代碼生成的所有資料庫命令的原始文本。 您還可以訂閱跟蹤事件:上下文中的 CommandExecuting、CommandFinished 和 CommandFailed。 利用這些事件,您可以在執行原始 DbCommand 的前後訪問它,以便您可以分析或記錄其他詳細資訊。

您可以從 MSDN 代碼庫 (code.msdn.microsoft.com/EFProviderWrappers) 中免費下載 EFTracingProvider 及其配套的 EFCachingProvider 和示例解決方案 EFProviderWrapperDemo(演示所有這些功能)。

協力廠商探查器

但是,您可能不想僅限於 EFTracingProvider 的日誌檔的原始文本。 您可以利用和學習這些日誌檔中的代碼,也可以利用兩個已經為您完成這項工作的工具。 以下是兩個用於分析實體框架查詢的協力廠商工具:Hibernating Rhinos 實體框架探查器和 Huagati 查詢探查器。

另外,LINQPad 不僅重點説明您在應用程式外部測試查詢運算式,還顯示您正在執行的 SQL 運算式。 儘管這是一個對於面向各種各樣的提供程式編寫 LINQ 的人員不可或缺的工具,但此工具不允許您分析由應用程式生成的查詢,因此我將不在本專欄中進一步討論它。

實體框架探查器 (EF Prof) 是 Hibernating Rhinos UberProf 系列探查器 (hibernatingrhinos.com/products/UberProf) 的一部分。 還有用於 nHibernate、Hibernate 和 LINQ to SQL 的探查器。 在撰寫本文時,第五個探查器 LLBLGen Pro 還處於測試階段。 EF Prof 將從其他 UberProf 工具派生的現有智慧財產權與從 EFTracingProvider 搜集的一些觀點結合起來。 簡單地說,您可以將一行代碼添加到應用程式使其能夠與 EF Prof 的引擎通信,並將結果報告在 EF Prof 用戶端應用程式中:

HibernatingRhinos.Profiler.  
    Appender.EntityFramework.
    EntityFrameworkProfiler.Initialize

資料庫活動由 ObjectContext 實例進行分組。在圖 4 中,您可以看到顯示了兩個 ObjectContext 實例,這是因為我運行了兩次示例代碼。

圖 4 EF Prof 查詢探查器 UI

圖 4 的右側,您還可以看到對所選上下文實例的每個資料庫調用的預覽。它顯示在 UPDATE 命令之後額外調用一個 SELECT。這實際上是與 SaveChanges 一起發送的命令的一部分,因為實體框架要確保向客戶實例返回 Customer 行的已更新時間戳記欄位。

當在 UI 中突出顯示 SQL 語句時,您可以在下半螢幕上看到完整的 SQL 以及指出的一個事實,即將值 5(此情況下)作為參數 @EntityKeyValue1 傳入。

利用 EF Prof,您還可以看到查詢生成的行,甚至可以看到資料庫查詢計畫。使用圖 5 中顯示的“堆疊跟蹤”選項卡,您可以瞭解應用程式如何執行特定的命令,甚至可以讓您直接跳到 Visual Studio 中的代碼行。

圖 5 EF Prof 堆疊跟蹤讓您跳到執行所選資料庫命令的代碼

EF Prof 能夠捕獲應用程式的所有實體框架活動並將其顯示在一個易於導航的 UI 中,其中還附加了一些有用的元件(如查詢計畫視圖)以及回到執行代碼的連結。EF Prof 的標準許可證是 305 美元,多個許可證和一個訂閱計畫會獲得折扣。EF Prof 適用于任意實體框架資料提供程式,因此不局限于 SQL Server。它適用于 .NET Framework 版本 3.5 和 4。

Huagati 查詢探查器最初稱為“L2S 探查器”,在十一月得到更新,添加了對實體框架 4 的支援。您也可以使用它來分析 LINQ to SQL 和 LLBLGen Pro,但它目前僅適用于 SQL Server。

實現查詢探查器就是在應用程式中引用探查器的程式集 (Huagati.EFProfiler.dll),並將兩個新構造函數以及一些其他邏輯添加到分部類中的 ObjectContext 類。圖 6 顯示已為 AWEntities 類創建的分部類。

圖 6 與 Huagati 查詢探查器一起使用的分部類

string profilerOutput =
      System.IO.Path.Combine(System.Environment.GetFolderPath(
        Environment.SpecialFolder.Personal),
      @"EFProfiler\Samples");
    _profiler=new HuagatiEFProfiler.EFProfiler(this, profilerOutput, null, 
      HuagatiEFProfiler.ExecutionPlanMode.Actual, false);
   _profiler.LogError += EFProfiler_LogError;
  }
}

EFProfiler.GetConnection 方法掛接到資料庫以跟蹤其活動。在 Huagati 網站上,您可以詳細瞭解產生實體 EFProfiler 時可以使用的各種設置。

探查器收集其資料,然後將資料輸出到指定資料夾中的某個檔。然後,您可以在探查器的日誌資源管理器中打開該檔(如圖 7 所示)。

圖 7 Huagati 實體框架查詢探查器 UI

如您在圖 7 中所見,收集了所有五個數據庫請求。Update 命令與其 SELECT 命令一起返回時間戳記,這正是該命令發送至資料庫的方式。

圖 7 中顯示的日誌資源管理器顯示 SQL Server 的 SQL 事件探查器資料中的相關行。與 EF Prof 一樣,您可以看到查詢及其參數,在 Stack 視圖中連結回應用程式中的相關代碼行,查看執行計畫,以及瞭解有關查詢的一些統計資訊。

每個上下文實例的資料庫活動均存儲在單獨的日誌檔中,因此日誌資源管理器一次將只顯示一組命令。利用這些設置,您可以用顏色標識警報以突出顯示異常的活動級別,如執行時間明顯甚至令人驚訝地長。

查詢探查器 UI 不像 EF Prof UI 那麼靈活,您需要在代碼中稍微加大一些投資(將邏輯添加到應用程式的每個 ObjectContext 中)。但元件是可分發的,這意味著您可以為用戶端環境中運行的應用程式收集探查器資訊。另外,它擁有的分析選項也沒有 EF Prof 那麼多。但對於許多開發人員而言,標準版標價 20 美元、專業版標價 40 美元(包括所有這三個探查器)可能彌補了這些差異。請記住,在我做探索時 Huagati 實體框架探查器仍處於測試階段,它僅適用于 SQL Server,不能像 EF Prof 一樣,可適用于任何支援實體框架的可用 ADO.NET 資料提供程式。

可在 tinyurl.com/26cfful 上找到 Hugati 的實體框架支援簡介。在這篇博客文章的結尾,您將發現測試版 1.31 的下載連結。

為相應的任務選擇合適的工具

我一直信奉“要為相應的任務選擇合適的工具”,當有其他非常好的工具的情況下,嘗試利用 Visual Studio 2010 費盡心機地來實現類似的功能是一種浪費。在本專欄中,您瞭解了內置到實體框架 API 和 Visual Studio 2010 的擴展工具組,它將為您提供原始資料和兩個協力廠商工具,這兩個工具不僅執行資料收集任務,還呈現資料。無論您選擇這些路徑中的哪個路徑,即使您只使用 SQL 事件探查器,在分析您的應用程式時也不應想當然地使用資料庫。

Julie Lerman* 是 Microsoft MVP、.NET 導師和顧問,住在佛蒙特州的山區。您可以在全球的使用者組和會議中看到她對資料訪問和其他 Microsoft .NET 主題的演示。她是受到廣泛稱讚的《Programming Entity Framework》(O'Reilly Media,2010)一書的作者,她的博客位址是 thedatafarm.com/blogjulielerman。請通過 Twitter.com 關注她:。*

衷心感謝以下技術專家對本文的審閱:Jarek Kowalski