十年河东,十年河西,莫欺少年穷。html
EF就如同那个少年,ADO.NET则是一位壮年。毕竟ADO.NET出生在EF以前,而EF所走的路属于应用ADO.NET。sql
也就是说:你所写的LINQ查询,最后仍是要转化为ADO.NET的SQL语句,转化过程当中无形下降了EF的执行效率。数据库
可是,使用EF的一个好处就是系统便于维护,减小了系统开发时间,下降了生成成本。数组
OK,上述只是作个简单的对比,那么在实际编码过程当中,咱们应当怎样提高EF的性能呢?服务器
工欲善其事,必先利其器。并发
咱们使用EF和在很大程度提升了开发速度,不过随之带来的是不少性能低下的写法和生成不过高效的sql。app
虽然咱们可使用SQL Server Profiler来监控执行的sql,不过我的以为实属麻烦,每次须要打开、过滤、清除、关闭。ide
在这里强烈推荐一个插件MiniProfiler。实时监控页面请求对应执行的sql语句、执行时间。简单、方便、针对性强。工具
如图:post
关于MiniProfiler的使用,你们可参考:MiniProfiler工具介绍(监控加载用时,EF生成的SQL语句)--EF,迷你监控器,哈哈哈
一、EF使用SqlQuery
上述已经说的很明白了,EF效率低于ADO.NET是由于LINQ-TO-SQL的过程消耗了时间。而使用SqlQuery则能够直接写SQL语句。
固然,若是你想获得更快的执行速度,你也能够在数据库上写存储过程PROC
关于SqlQuery的用法,在此不做解释。
二、EF使用AsNoTracking(),无跟踪查询技术(查询出来的数据不能够修改,若是你作了修改,你会发现修改并不成功)
2.一、测试修改:
var student = context.Student.AsNoTracking().Where(A => A.Id == 2).FirstOrDefault() ; student.StuName = "毛毛"; context.SaveChanges();
上述代码尝试修改数据,程序运行完之后,咱们会发现数据库Id为2的学生的姓名并无修改,所以,采用无跟踪查询技术获得的数据是不能够进行修改的。
2.二、性能测试:
代码测试以下:
public ActionResult Index() { var profiler = MiniProfiler.Current; using (profiler.Step("高性能查询Student的数据")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var a = context.Student.AsNoTracking().Where(A => A.StuName.Contains("张")).ToList(); } } using (profiler.Step("查询Student的数据")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b = context.Student.Where(A => A.StuName.Contains("张")).ToList(); } } return View(); }
性能对好比下:
注意:(由于我使用的是本地数据库,因此效率差异不是很大,若是是远程数据库且数据量比较大,性能会提高不少,有测试证实:其性能可提高4~5倍)
三、性能提高之AsNonUnicode
代码测试以下:
public ActionResult Index() { var profiler = MiniProfiler.Current; using (profiler.Step("查询Student的数据")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b = context.Student.Where(A => A.StuName=="赵刚").ToList(); } } using (profiler.Step("高性能查询Student的数据")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var a = context.Student.AsNoTracking().Where(A => A.StuName == DbFunctions.AsNonUnicode("赵刚")).ToList(); } } return View(); }
性能对好比下:
从上图能够看出,生成了两条基本相同的SQL语句,惟独不相同的地方是:不加AsNonUnicode SQL中会有 N,加了AsNonUnicode后,SQL中没有N
使用 N 前缀(查询过程当中须要把数据库默认格式转化为Unicode 格式来查询,所以:性能被拉低)
在服务器上执行的代码中(例如在存储过程和触发器中)显示的 Unicode 字符串常量必须以大写字母 N 为前缀。即便所引用的列已定义为 Unicode 类型,也应如此。
不使用 N 前缀
若是不使用 N 前缀,字符串将转换为数据库的默认代码格式。这可能致使不识别某些字符。
所以,关于 AsNonUnicode 的的使用,还要结合具体状况。
四、多字段组合排序(字符串)先按照学号排序,再按姓名排序(请将排序OrderBy放在构造LINQ的最后)
错误代码以下:
using (profiler.Step("查询Student的数据")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b1 = context.Student.Where(A => A.StuName.StartsWith("王")).OrderBy(A => A.StuNum).OrderBy(A => A.StuName).ToList(); } }
正确代码以下:
using (profiler.Step("高性能查询Student的数据")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b2 = context.Student.Where(A => A.StuName.StartsWith("王")).OrderBy(A => A.StuNum).ThenBy(A => A.StuName).ToList(); } }
由上图获得的结果分析可知:错误代码连续使用两个OrderBy,致使后面的OrderBy覆盖了前面的OrderBy,也就是说:错误代码是按照姓名排列的。
所以,涉及连续排序时,要用ThenBy。
五、foreach循环的陷进
5.一、关于延迟加载
请看上图红框。为何StudentId有值,而Studet为null?由于使用code first,须要设置导航属性为virtual,才会加载延迟加载数据。
加了virtual后,咱们就可使用延迟加载了。可是,若是用上述的ForEach循环,会产生严重的性能问题。
以下:
咱们经过 MiniProfiler工具 监控下生成的SQL语句,以下
生成了101条SQL语句,是否是很吓人。
那咱们应当怎么正确的使用懒加载呢?
解决方案:使用Include显示链接查询(注意:须要手动导入using System.Data.Entity 否则Include只能传表名字符串)。
加上了Include后,懒加载就变成了显示加载,也就是说带有Virtual的懒加载字段信息会被一次加载出来,所以:使用 Include 后,只会生成一条SQL语句!
再看MiniProfiler的监控(瞬间101条sql变成了1条,这其中的性能可想而知。)
所以,性能会大大滴提高哦。
六、AutoMapper的使用
所谓AutoMapper即:自动映射,关于AutoMapper的使用,你们可参考个人博客:AutoMapper自动映射
下面结合数据库来看以下示例:
数据表关系:
create table Dept ( Id int identity(1,1) not null, deptNum varchar(20) not null primary key, deptName nvarchar(20) default('计算机科学与工程系'), ) create table Student ( Id int identity(1,1) not null, StuNum varchar(20) primary key, deptNum varchar(20) FOREIGN KEY (deptNum) REFERENCES Dept (deptNum), StuName nvarchar(10),-- StuSex nvarchar(2) default('男'), AddTime datetime default(getdate()), )
很简单。系表和学生表,有个外键deptNum,
EF中生成的DTO以下:
namespace BingFa.Entity { using System; using System.Collections.Generic; public partial class Student { public int Id { get; set; } public string StuNum { get; set; } public string deptNum { get; set; } public string StuName { get; set; } public string StuSex { get; set; } public Nullable<System.DateTime> AddTime { get; set; } public virtual Dept Dept { get; set; } } } namespace BingFa.Entity { using System; using System.Collections.Generic; public partial class Dept { public Dept() { this.Student = new HashSet<Student>(); } public int Id { get; set; } public string deptNum { get; set; } public string deptName { get; set; } public virtual ICollection<Student> Student { get; set; } } }
Model层
public class StudentModel { public int Id { get; set; } public string StuNum { get; set; } public string deptNum { get; set; } public string StuName { get; set; } public string StuSex { get; set; } public Nullable<System.DateTime> AddTime { get; set; } public string deptName { get; set; } }
测试代码以下:
由上述代码得知,咱们须要根据导航属性获取系名。
同理,若是你有不少导航属性,你亦能够多写几回 ForMember(......) ,可是这样作会陷入延迟加载的陷阱。
针对上述的写法,咱们的监测以下:
能够看出居然生成了两条SQL语句,若是你用了N个导航属性,那么就会生成N+1个SQL语句,这显然是不能接受的,怎么办呢?
同上述,ForEach的陷阱同样,咱们能够派上Include,以下:
加上了AsNoTracking无跟踪查询技术,这个是用来提高查询性能。同时加上了Include,用于显示加载,从而避免了懒加载生成SQL的问题。
监测以下:
由此可知,仅仅生成了一条SQL语句,SQL查询性能也提高了不少,所以在使用AutoMapper时,切记别陷入这种陷阱。
其实,说白了,其实都是懒加载惹的祸,用很差的话,懒加载会让你很累的哦。
七、count(*)被你用坏了吗(Any的用法)
要求:查询是否存在名字为“张三2”的学生。(你的代码会怎样写呢?)
用第一种?第二种?第三种?呵呵,我之前就是使用的第一种,而后有人说“你count被你用坏了”,后来我想了想了怎么就被我用坏了呢?直到对比了这三个语句的性能后我知道了。
看到监控后,瞬间惊呆了,count(*)的性能居然最低,Any的性能最高。性能之差竟有三百多倍,count确实被我用坏了。(我想,不止被我一我的用坏了吧。)
咱们看到上面的Any干吗的?官方解释是:
我反复阅读这个中文解释,一直没法理解。甚至早有人也提出过一样的疑问《实在看不懂MSDN关于 Any 的解释》
因此我我的理解也是“肯定集合中是否有元素知足某一条件”。咱们来看看any其余用法:
要求:查询教过“张三”或“李四”的老师
实现代码:
两种方式,之前我会习惯写第一种。固然咱们看看生成过的sql和执行效率以后,见解改变了。
效率之差竟有近六倍。
咱们再对比下count:
得出奇怪的结论:
八、动态建立LINQ子查询
查询姓 张 李 王 的男人
LINQ 以下:
var Query = from P in persons1 where (P.Name.Contains("张") || P.Name.Contains("李") || P.Name.Contains("王"))&&P.Sex=="男" select new PersonModel { Name = P.Name, Sex = P.Sex, Age = P.Age, Money = P.Money };
如今需求变动以下:查询姓 张 李 王 的男人 而且 年龄要大于20岁
LINQ 变动以下:
var Query = from P in persons1 where (P.Name.Contains("张") || P.Name.Contains("李") || P.Name.Contains("王"))&&P.Sex=="男"&&P.Age>20 select new PersonModel { Name = P.Name, Sex = P.Sex, Age = P.Age, Money = P.Money };
好了,若是您认为上述构建WHERE子句的方式就是动态构建的话,那么本篇博客就没有什么意义了!
那么什么样的方式才是真正的动态构建呢?
OK,我们进入正题:
在此我提出一个简单需求以下:
我相信个人需求提出后,你用上述方式就写不出来了,个人需求以下:
请根据数组中包含的姓氏进行查询:
数组以下:
string[] xingList = new string[] { "赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈" };
在这里,有人可能会立马想到:分割数组,而后用十个 || 进行查询就好了!
我要强调的是:若是数组是动态的呢?长度不定,包含的姓氏不肯定呢?
呵呵,想必写不出来了吧!
还好,LINQ也有本身的一套代码能够实现(若是LINQ实现不了,那么早就没人用LINQ了):
因为代码比较多,在此你们可参考:LINQ 如何动态建立 Where 子查询
代码以下:
须要指出的是:
Expression.Or(con, condition); 逻辑或运算
Expression.And(con, condition); 逻辑与运算
代码分析:
生成的LINQ子查询相似于:c=>c.Tags.Contains(s) || c=>c.Alias.Contains(Alias)....
九、真分页与假分页(了解 IQueryable,IEnumerable的区别)
你们都知道分页是很是经常使用的功能,可是在使用EF写分页语句的时候,稍有不慎,真分页便会成为假分页:
上述两个看似相似的LINQ语句,实际执行起来效率差了不少。其缘由是ToList使用的位置,当你ToList()时,EF会将linq转化为SQL,而后执行。
第一个LINQ咱们可理解为:先把数据所有都查询出来,而后分页
第二个LINQ咱们可理解为:只查询分页所需的N条数据。若是你有100万条数据,第一种方法会所有查询出来,第二种方法仅仅会查询分页所需的10条数据,其性能对比可想而知。
十、批量删除和修改
不知道你是否研究过EF的插入删除和修改操做,当你批量操做数据的时候,经过SQL Server Profiler能够明显看到产生了大量的Insert,Update语句,效率很是低;由于他插入一条数据,会对应生成一条Insert语句,当你的list中有10万条数据时,就会生成10万条插入语句!不过还好我们有对策:Entity Framework Extendeds ,EF扩展类完美解决批量操做问题:
要使用AddRange,一次性插入10万条数据。
十一、EF使用存储过程
在此贴出个人存储过程(我这个存储过程也是处理并发的存储过程),关于并发处理你们可参考:C# 数据库并发的解决方案(通用版、EF版)
create proc LockProc --乐观锁控制并发 ( @ProductId int, @IsSuccess bit=0 output ) as declare @count as int declare @flag as TimeStamp declare @rowcount As int begin tran select @count=ProductCount,@flag=VersionNum from Inventory where ProductId=@ProductId update Inventory set ProductCount=@count-1 where VersionNum=@flag and ProductId=@ProductId insert into InventoryLog values('插入一条数据,用于计算是否发生并发',GETDATE()) set @rowcount=@@ROWCOUNT if @rowcount>0 set @IsSuccess=1 else set @IsSuccess=0 commit tran
EF执行存储过程的方法以下:
十二、EF Contains、StartsWith、EndsWith
请看以下代码:
生成了按照Unicode字符集进行的模糊查询,生成的SQL带N
如何优化呢?首先咱们按照本篇博客第三条:三、性能提高之AsNonUnicode 咱们按照数据库默认编码查询来提高效率。
根据生成的SQL语句,能够看出查询没有带N,执行时间为32.4秒,效率增长一倍。
除了上述优化以外,还要看公司项目的具体要求,若是要求进行双向匹配,那么你只能老老实实的采用Contains,若是公司只要求单项匹配,你能够采用StartsWith、EndsWith
固然,要想模糊查询相率高些,单项匹配固然最好,具体还要看项目需求哦
1三、EF预热
使用过EF的都知道针对全部表的第一次查询都很慢,而同一个查询查询过一次后就会变得很快了。
假设场景:当咱们的查询编译发布部署到服务器上时,第一个访问网站的的人会感受到页面加载的十分缓慢,这就带来了很很差的用户体验。
解决方案:在网站初始化时将数据表遍历一遍
在Global文件的Application_Start方法中添加以下代码(代码以下(Entity Framework的版本至少是6.0才支持)):
using (var dbcontext = new BingFaTestEntities()) { var objectContext = ((IObjectContextAdapter)dbcontext).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); }
咱们作个测试:
12.一、第一次运行程序,不进行EF预热的:
12.二、一样从新运行程序,进行EF预热的:
执行速度:
由上图能够,在进行了EF预热后,加载时间为856.9毫秒,而不进行EF预热加载用时1511.5毫秒,由此可知,加上预热代码后,第一次加载速度几乎快了一倍。