七色花基本权限系统(12)- 优化EntityFramework的配置
EF性能渣?这个锅EF不背
作为ORM,必然有性能上的损失,这是取舍问题。但“舍”的性能损失,大到可以用渣形容吗?
微软在设计EF的时候同时考虑了CS和BS,导致部分配置适合A却不适合B,而可能为了让开发者(无论CS系统开发者还是BS系统开发者)体验到这些配置,默认都是开启的。这直接成为部分开发者认为EF性能很渣的重要原因之一。
这篇日志将粗略介绍几个主要配置的作用和适用场景。
最常用的几个主要配置
打开数据库上下文类(MasterEntityContext.cs),该类继承于System.Data.Entity.DbContext,转到其定义,可以看到一个只读属性Configuration,再转到其定义,就可以看到完整的“上下文的配置选项”,一共6个,如下图:
这6个属性都挺重要的,不需要知道其内部实现,但至少了解作用和适用场景。
第一个属性AutoDetectChangesEnabled,表示是否自动追踪数据变化。
举个简单的例子:
1 var user = db.Users.find(3); 2 user.Password = "000"; 3 4 db.SaveChanges();
以上代码,在SaveChanges时,组装的update语句中,是包含对SysUser的所有属性的update,还是只包含Password属性?
这由配置属性AutoDetectChangesEnabled决定。当该属性是true时,表示自动追踪数据变化,那么在SaveChanges时会自动检测出“哪些属性被修改”,也就能组装出干净的sql语句;而当该属性是false时,表示不追踪数据变化,那么最终的sql语句将包含SysUser所有属性的更改。
还有一种更新实体的写法是这样:
1 var user = db.Users.find(3); 2 user.Password = "000"; 3 4 db.Users.Attach(user);//表示将整个user附加给db,即认为所有属性都是改变了 5 db.Entry(user).State = System.Data.Entity.EntityState.Modified;
一旦附加,无论AutoDetectChangesEnabled属性是如何设置的,都将在update-sql语句中包含所有属性。
可见AutoDetectChangesEnabled这个属性在BS开发下几乎发挥不了作用,经过http传递的数据在后台接收之后必然是一个新的实体模型对象,需要附加给db,而附加操作就是表示需要对所有属性进行update。
第二个属性EnsureTransactionsForFunctionsAndCommands,表示是否应在事务中始终执行 SQL 函数和命令。
EF暴露了直接写sql语句执行的接口,而这个属性就表示在执行这种sql时是否自动嵌套事务。该属性与BS开发并无必然联系,可根据情况开启或关闭,默认是开启的。
第三个属性LazyLoadingEnabled,应该和第四个属性ProxyCreationEnabled一起讲。
前者表示是否启用延迟加载,后者表示是否启用代理创建,延迟加载依赖于代理创建。
我们会使用这两个设置来决定是否加载导航属性。默认情况这两个值都是true的,也就是说会以延迟加载的方式加载导航属性,也就是当我们访问导航属性的时候才会去查数据库获取导航属性的数据。
但通常情况下,BS开发时不建议使用ORM的导航属性功能。换句话说就是,不建议为实体之间建立级联关系。
所以这两个属性在BS开发下也是应该关闭的。
第五个属性UseDatabaseNullSemantics,表示是否展示数据库null语义。
这个跟如何翻译sql有关,比如在mssql中,where A = ‘’和 where A is null 是不同的,因此当linq语句的Where(w => w.A == a)中变量a是null时,就需要告诉EF应该翻译成空呢还是翻译成null还是两者兼顾。这个可根据情况设置。
第六个属性ValidateOnSaveEnabled,表示在SaveChanges时是否自动验证实体。
实体类配置中可以对各属性添加验证条件,比如非空、长度限制等。当该属性为true时,会在提交时自动验证。而通常情况下,前端验证加上后端的业务验证,就可以了,数据库级别的验证,交给数据库好了。所以在BS开发下这个属性也可以关闭。
这么一来,配置一下以上属性吧。在数据库上下文类(MasterEntityContext)的构造方法中,加上这几句代码:
1 // 禁用实体状态改变跟踪 2 Configuration.AutoDetectChangesEnabled = false; 3 // 禁用导航属性延迟加载 4 Configuration.LazyLoadingEnabled = false; 5 // 禁用自动创建代理类 6 Configuration.ProxyCreationEnabled = false; 7 // 禁用数据库null语义 8 Configuration.UseDatabaseNullSemantics = true; 9 // 禁用保存时验证所跟踪实体 10 Configuration.ValidateOnSaveEnabled = false;
不过由于该类是T4自动生成的,所以应该把这几句代码加上对应的T4模板中去。
简单测试插入万级数据
此处将比较ADO.NET和EF在插入万级别数据时的耗时。
此处直接放测试结果,测试的代码会在源码中。
运行ADO.NET插入10000条用户数据的测试,结果如下图:
现在测一下EF插入10000条用户数据的耗时:
测试结果也很明显,万条数据插入多700毫秒左右的耗时。中小型系统,基本不必考虑这种万级别的数据插入,即使有也是部分特例。而体量更小的情况下,差距可以基本忽略不计。那么问题只剩下一个:舍弃这部分性能,用来换开发效率是否值得?
注:没有把测试代码单独放在单元测试里是因为,在单元测试里测试数据插入,会放大耗时,无论是ADO.NET还是EF,都会被放大70%左右,我也不知道为啥。
EF配置相关不是本系列的重点,因此简单带过。
下一章节,将演示业务层的使用以及让数据层与WebUI层解耦。
截止本章节,项目源码下载:点击下载(存在百度云盘中)