【转】你必须知道的EF知识和经验

【转】你必须知道的EF知识和经验html

注意:如下内容若是没有特别申明,默认使用的EF6.0版本,code first模式前端

推荐MiniProfiler插件

工欲善其事,必先利其器。git

咱们使用EF和在很大程度提升了开发速度,不过随之带来的是不少性能低下的写法和生成不过高效的sql。github

虽然咱们可使用SQL Server Profiler来监控执行的sql,不过我的以为实属麻烦,每次须要打开、过滤、清除、关闭。sql

在这里强烈推荐一个插件MiniProfiler。实时监控页面请求对应执行的sql语句、执行时间。简单、方便、针对性强。数据库

如图:(具体使用和介绍请移步)oracle

数据准备

新建实体:Score(成绩分数表)、Student(学生表)、Teacher(老师表)app

后面会给出demo代码下载连接工具

foreach循环的陷进 

1.关于延迟加载性能

请看上图红框。为何StudentId有值,而Studet为null?由于使用code first,须要设置导航属性为virtual,才会加载延迟加载数据。

2.关于在循环中访问导航属性的异常处理(接着上面,加上virtual后会报如下异常

"已有打开的与此 Command 相关联的 DataReader,必须首先将它关闭。"

解决方案:

  • 方案一、设定ConnectionString加上MultipleActiveResultSets=true,但只适用于SQL 2005之后的版本
  • 方案二、或者先读出放置在List中

3.以上两点仅为热身,咱们说的陷阱才刚刚开始!

而后咱们点击打开MiniProfiler工具(不要被吓到

解决方案:使用Include显示链接查询(注意:须要手动导入using System.Data.Entity 否则Include只能传表名字符串)。

再看MiniProfiler的监控(瞬间101条sql变成了1条,这其中的性能可想而知。

AutoMapper工具

上面咱们经过Include显示的执行表的链接查询显然是不错的,但还不够。若是咱们只须要查询数据的某些字段呢,上面查询全部字段岂不是很浪费内存存储空间和应用程序与数据库数据传输带宽。

咱们能够:

对应监控到的sql:

咱们看到生成的sql,查询的字段少了不少。只有咱们显示列出来字段的和一个StudentId,StudentId用来链接查询条件的。

是的,这样的方式很不错。但是有没有什么更好的方案或方式呢?答案是确定的。(否则,也不会在这里屁话了。)若是表字段很是多,咱们须要使用的字段也很是多,导航属性也很是多的时候,这样的手动映射就显得不那么好看了。那么接下来咱们开始介绍使用AutoMapper来完成映射:

注意:首先须要NuGet下载AutoMapper。(而后导入命名空间 using AutoMapper; using AutoMapper.QueryableExtensions;)

咱们看到上面查询语句没有一个个的手动映射,而映射都是独立配置了。其中CreateMap应该是要写到Global.asax文件里面的。其实也就是分离了映射部分,清晰了查询语句。细心的同窗可能注意到了,这种方式还免去了主动Include

咱们看到了生成的sql和前面有些许不一样,但只生成了一条sql,而且结果也是正确的。(其实就是多了一条CASE WHEN ([Extent2].[Id] IS NOT NULL) THEN 1 END AS [C1]。看起来这条语句并无什么实际意义,然而这是AutoMapper生成的sql,同时我也表示不理解为何和EF生成的不一样)

这样作的好处?

  1. 避免在循环中访问导航属性屡次执行sql语句。
  2. 避免了查询语句中太多的手动映射,影响代码的阅读。

关于AutoMapper的其余一些资料:

http://www.cnblogs.com/xishuai/p/3712361.html

http://www.cnblogs.com/xishuai/p/3700052.html

http://www.cnblogs.com/farb/p/AutoMapperContent.html

联表查询统计

要求:查询前100个学生考试类型(“模拟考试”、“正式考试”)、考试次数、语文平均分、学生姓名,且考试次数大于等于3次。(按考试类型分类统计

代码以下:

看到这样的代码,我第一反应是惨了。又在循环执行sql了。监控以下:

其实,咱们只须要稍微改动就把101条sql变成1条,以下:

立刻变1条。

咱们打开查看详细的sql语句

发现这仅仅只是查询结果集合而已,其中的按考试类型来统计是程序拿到全部数据后在计算的(而不是在数据库内计算,而后直接返回结果),这样一样是浪费了数据库查询数据传输。

关于链接查询分组统计咱们可使用SelectMany,以下:

监控sql以下:(是否是简洁多了呢?

关于SelectMany资料:

http://www.cnblogs.com/lifepoem/archive/2011/11/18/2253579.html

http://www.cnblogs.com/heyuquan/p/Linq-to-Objects.html

性能提高之AsNonUnicode

监控到的sql

咱们看到EF正常状况生成的sql会在前面带上“N”,若是咱们加上DbFunctions.AsNonUnicode生成的sql是没有“N”的,当你发现带上“N”的sql比没有带“N”的 sql查询速度慢不少的时候那就知道该怎么办。

之前用oracle的时候带不带“N”查询效率差异特别明显,今天用sql server测试并无发现什么差异。还有我发现EF6会根据数据库中是nvarchar的时候才会生成带“N”的sql,oracle数据库没测试,有兴趣的同窗能够测试下

性能提高之AsNoTracking

咱们看生成的sql

sql是生成的如出一辙,可是执行时间倒是4.8倍。缘由仅仅只是第一条EF语句多加了一个AsNoTracking。

注意:

  • AsNoTracking干什么的呢?无跟踪查询而已,也就是说查询出来的对象不能直接作修改。因此,咱们在作数据集合查询显示,而又不须要对集合修改并更新到数据库的时候,必定不要忘记加上AsNoTracking。
  • 若是查询过程作了select映射就不须要加AsNoTracking。如:db.Students.Where(t=>t.Name.Contains("张三")).select(t=>new (t.Name,t.Age)).ToList();

多字段组合排序(字符串)

要求:查询名字里面带有“张三”的学生,先按名字排序,再按年龄排序。

咦,不对啊。按名字排序被年龄排序覆盖了。咱们应该用ThenBy来组合排序。

不错不错,正是咱们想要的效果。若是你不想用ThenBy,且都是升序的话,咱们也能够:

生成的sql是同样的。与OrderBy、ThenBy对应的降序有OrderByDescending、ThenByDescending。

看似好像很完美了。其实否则,咱们大多数状况排序是动态的。好比,咱们会更加前端页面不一样的操做要求不一样字段的不一样排序。那咱们后台应该怎么作呢?

固然,这样完成是没问题的,只要你愿意。能够这么多可能的判断有没有感受很是SB?是的,咱们固然有更好的解决方案。要是OrderBy能够直接传字符串???

解决方案:

  1. guget下载System.Linq.Dynamic 
  2. 导入System.Linq.Dynamic命名空间
  3. 编写OrderBy的扩展方法

而后上面又长又臭的代码能够写成:

咱们看下生成的sql:

和咱们想要的效果彻底符合,是否是感受美美哒!!

【注意】:传入的排序字段后面要加排序关键字 asc或desc

lamdba条件组合

要求:根据不一样状况查询,可能状况

  1. 查询name=“张三” 的全部学生
  2. 查询name=“张三” 或者 age=18的全部学生

实现代码:

是否是味到了一样的臭味。下面咱们来灵活组装Lamdba条件。

解决方案:

这段代码我也是从网上偷的,具体连接找不到了。

而后咱们的代码能够写成:

有没有美美哒一点。而后咱们看看生成的sql是否正确:

EF的预热

http://www.cnblogs.com/dudu/p/entity-framework-warm-up.html

count(*)被你用坏了吗(Any的用法)

要求:查询是否存在名字为“张三”的学生。(你的代码会怎样写呢?

第一种?第二种?第三种?呵呵,我之前就是使用的第一种,而后有人说“你count被你用坏了”,后来我想了想了怎么就被我用坏了呢?直到对比了这三个语句的性能后我知道了。

性能之差竟有三百多倍,count确实被我用坏了。(我想,不止被我一我的用坏了吧。

咱们看到上面的Any干吗的?官方解释是:

我反复阅读这个中文解释,一直没法理解。甚至早有人也提出过一样的疑问《实在看不懂MSDN关于 Any 的解释

因此我我的理解也是“肯定集合中是否有元素知足某一条件”。咱们来看看any其余用法:

要求:查询教过“张三”或“李四”的老师

实现代码:

两种方式,之前我会习惯写第一种。固然咱们看看生成过的sql和执行效率以后,见解改变了。

效率之差竟有近六倍

咱们再对比下count:

得出奇怪的结论:

  1. 在导航属性里面使用count和使用any性能区别不大,反而FirstOrDefault() != null的方式性能最差。
  2. 在直接属性判断里面any和FirstOrDefault() != null性能区别不大,count性能要差的多。
  3. 因此,不论是直接属性仍是导航属性咱们都用any来判断是否存在是最妥当的

透明标识符

假如因为各类缘由咱们须要写下面这样逻辑的语句

咱们能够写成这样更好

看生成的sql就知道了

第二种方式生成的sql要干净得多,性能也更好。

EntityFramework.Extended

这里推荐下插件EntityFramework.Extended,看了下,很不错。

最大的亮点就是能够直接批量修改、删除,不用像EF默认的须要先作查询操做。

至于官方EF为何没有提供这样的支持就不知道了。不过使用EntityFramework.Extended须要注意如下几点:

  1. 只支持sql server
  2. 批量修改、删除时不能实现事务(也就是出了异常不能回滚)
  3. 没有联级删除
  4. 不能同EF一块儿SaveChanges详见

http://www.cnblogs.com/GuZhenYin/p/5482288.html

在此纠正个问题EntityFramework.Extended并非说不能回滚,感谢@GuZhenYin园友的指正(原谅我以前没有动手测试)。

注意:须要NuGet下载EntityFramework.Extended, 并导入命名空间: using EntityFramework.Extensions ;

测试代码以下:(若是注释掉手抛异常代码是能够直接更新到数据库的)

using (var ctxTransaction = db.Database.BeginTransaction()) { try { db.Teachers.Where(t => true).Update(t => new Teacher { Age = "1" }); throw new Exception("手动抛出异常"); ctxTransaction.Commit();//提交事务
 } catch (Exception) { ctxTransaction.Rollback();//回滚事务
 } }

自定义IQueryable扩展方法

 最后整理下自定义的IQueryable的扩展。

 

 

补充1:

First和Single的区别:前者是TOP(1)后者是TOP(2),后者若是查询到了2条数据则抛出异常。因此在必要的时候使用Single也不会比First慢多少。

补充2: 

已打包nuget提供直接安装 Install-Package Talk.Linq.Extensions 或nuget搜索 Talk.Linq.Extensions 

https://github.com/zhaopeiym/Talk/wiki/Talk.Linq.Extensions_demo

 

结束:

源码下载:http://pan.baidu.com/s/1o8MYozw

本文以同步至《C#基础知识巩固系列

欢迎热心园友补充!

相关文章
相关标签/搜索