ADONET团队最近讨论了ADONET Entity框架的各种性能特征ADONET Entity框架在月已经进入它的第三个beta版本自那时起开发团队就开始为开发人员提供了使用该框架的相关信息而现在则为开发人员提供了框架性能方面的信息
本文鞭辟入里地介绍了ADONET Entity框架的性能演示了如何提高简单查询速度的方法并阐释了框架的性能特征
需要重点指出的是当一个抽象层或者类似EDM(译注指Entity Data Model)的模块被用来转换数据库的关系样式时会带来一定的性能损失
查询与结果
本文使用了NorthWind数据库作为模型并创建了一个简单查询
(NorthwindEntities ne = NorthwindEntities()) { (Order o ne
Orders) { i = o
OrderID; } }
测试时我们的每个查询对整个行数据进行了次遍历结果很有意思第次运行时耗费了毫秒而接下来的每次运行则平均耗费毫秒左右的时间最耗时的一部分内容是ObjectContext的创建而在执行任意一个访问数据库的操作时都会有一些耗时的操作发生
每次操作的百分比值可以给我们一些启示
装载元数据(%)
初始化元数据(%)
打开连接(%)
生成视图(%)
装载程序集(%)
跟蹤(%)
实例化(%)
其它(%)
耗时百分比值最大的是视图生成它达到了惊人的%既然视图生成是造成性能损耗的罪魁祸首那么开发人员最好是使用命令行工具EDM生成器(EdmGenexe)运行时需要加上视图生成命令参数(/mode:ViewGeneration)它的输出内容为一个代码文件(C#或者VBNET)可以包含在项目中视图的预生成可以将启动时间降低到毫秒而对于循环遍历操作整个时间可以降低%生成视图并随着应用程序一起发布是提高性能的妙方但其缺点则在于视图不再是动态的一旦模型发生改变就需要重新生成以保持同步
查询性能
需要指出的是关于性能的主要设计要素是查询缓存一旦执行了查询它的一部分内容就被维持在全局缓存中由于查询与元数据缓存的存在使得第二次运行的执行速度总是比第一次运行快例如如下的Entity SQL查询
(PerformanceArticleContext ne = PerformanceArticleContext()) { ObjectQuery<Orders> orders = ne
CreateQuery<Orders>(); (Orders o orders) { i = o
OrderID; } }
第一次运行该查询耗时毫秒但下一次运行则只耗费了毫秒的时间首次运行与后续运行在执行方面的区别在于它构建了能够为执行传递provider的命令树(command tree)
LINQ查询在执行方式上与Entity SQL查询相似例如下面的查询
(PerformanceArticleContext ne = PerformanceArticleContext()) { var orders = from order ne
Orders select order; (Orders o orders) { i = o
OrderID; } }
首次执行LINQ查询耗时毫秒而随后的执行耗时毫秒两者的差距还要低于Entity SQL可以看到使用编译了的LINQ查询对于性能的提高更为明显编译LINQ查询的好处在于它构建了表达树(expression tree)当查询被编译时后续的执行就不需要重建表达树了编译的LINQ查询代码看起来像这样
Func<PerformanceArticleContext
IQueryable<Orders>> compiledQuery = CompiledQuery
Compile((PerformanceArticleContext ne) => (from o ne
Orders select o)); (PerformanceArticleContext ne = PerformanceArticleContext()) { (Orders o compiledQuery(ne)) { i = o
OrderID; } }
注意PerformanceArticleContext是一个委托对于编译了的LINQ查询而言第一次执行耗时毫秒而随后的执行时间则为毫秒结果并不惊人值得关注的是编译的LINQ查询比之常规方式的LINQ查询执行时间少了毫秒或许对于几个查询而言这算不上什么但如果有数以千计的查询这样的性能提升就倍显价值所在了
ADONET团队建议开发人员在查询中应谨慎使用Track/NoTrack选项
在之前的例子中所有放在对象创建中的查询结果都被添加到ObjectStateManager中因此我们能够跟蹤它们的更新如果没有必要跟蹤对象的更新和删除那么最好是使用NoTracking合并项例如在一个ASPNET Web应用程序中如果它要查询一个指定的分类名称但却不需要对返回的数据进行更新那么NoTracking就会是一个不错的选择在这种情形下使用NoTracking的查询会在性能方面得到改善
基于前面的一组数字NoTracking选项能够大幅度地降低执行的时间而其中性能的提升主要源自于我们停止了对变更的跟蹤以及对关系的管理如果使用NoTracking查询无论是第一次执行还是随后的执行编译的LINQ查询都要优于标准的LINQ查询注意编译的LINQ查询的第二次执行与Entity SQL查询的第二次执行相等
ADONET团队同时还提醒开发者在创建查询时有一些内容必须铭记于心
在Entity框架中优化查询性能时应该针对特定的编程场景做出最佳选择这里列举了几个关键项
ObjectContext的首次创建包含了装载和验证元数据的性能损耗
任何一个查询的首次执行都包含了构建一个查询缓存的性能损耗以利于提高后续查询的执行速度
编译的LINQ查询比未编译的LINQ查询要快
如果不需要跟蹤数据的变更与数据的关系或者对大数据对象进行流操作那么通过NoTracking合并项执行查询效果会更佳