数据点

分析实体框架中的数据库活动

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/blog。请通过 Twitter.com 关注她:julielerman

衷心感谢以下技术专家对本文的审阅:Jarek Kowalski