又是一个炎热夏日的晚上,开着空调听着音乐又开始了咱们今天的博文。此文并非ROM工具哪家强之类的引战贴,只是本文本身的一点见解和看法,望前辈看官有望斧正html
本文欢迎转载,原文地址:http://www.cnblogs.com/DjlNet/p/7220720.htmlgit
话说回顾历史的话,在linq to sql的年代到后面linq to entity也就是ef4.1以致于如今的ef6.1.3历经了好些岁月的打磨,且也用ef6在真实项目中使用体会到了在开发速度和维护成本体现出来的优点,固然并不表明它就是没有弱点或者是说要经过一些方式去规避不当使用形成的问题哈,因此一个东西不可能作到尽善尽美,毕竟在我看来它也只是基于C#3开始带来linq基础的上面的一层数据库访问的抽象,固然不光是查询啦,也包含写操做啦或者事务之类的,反正啦上层抽象只能作到它本身功能的抽取也就是包含关系型数据库你们都有的一些特性和操做,这样的抽象也是合情合理因此就在linq的基础上面就这样孕育而生,以及具体的话就须要交给数据库对应的提供器去解析表达式去适配数据库自个儿的一些特色和异同,这也是复杂和当心的过程由于这基本都关系着到达数据库执行的sql语句到底如何的问题,实际反应的就是产生的执行计划消耗等等,这其中也是其余ORM工具(暂且这样称呼吧,例如一些Dapper+Extension之类说实话有点勉强,注意这里并无故意贬低牛逼的Dapper的意思,毕竟我大Stackoverflow就是用的它,具体能够去github查看wiki)一样须要去作的一件事儿,固然这里不排除微软老爹对自家数据库sql server亲儿子有些小操做。github
这里要额外提一下:感谢@Pomelo大大对MySql实现ef core驱动的奉献,也获得了微软的支持,因此应该能够放心食用
,微软官方目前的数据库驱动列表:https://docs.microsoft.com/zh-cn/ef/core/providers/,其中ef core对于sql server有个批量操做的优化,偶尔看看issues得知,同时发现同事园子一老哥也发文说明了状况,具体传送门:https://github.com/aspnet/EntityFramework/issues/9270,这个貌似是一直被业界所诟病的问题,固然也能够自定义扩展或者特殊状况配合sql+transaction来处理批量写问题我以为也是能够的,以致于一些第三方ef batch框架(例如:entity framework plus)这里暂不评价了,我的以为毕竟带来方便的同时引入了复杂度web
好了屁话说了一大推,说到这里大家确定会觉得又是一个EF长篇大论文入门文,例如什么是ORM,什么是OTO,什么是Code First,这些介绍的太多太多了,建议仍是才看微软ef官方文档便可(https://msdn.microsoft.com/en-us/library/ee712907(v=vs.113).aspx)以及微软新上线的文档地址:https://docs.microsoft.com/en-us/ef/,也许能把官方文档大体浏览一边基本作到心中有数遇到问题再去翻翻便可,比我在这里BB可能或许有用,因此这里并不会对EF有一个详细的解读(固然作到详细解析,我也作到不到呀),这方面的博文已经数不胜数,在这里可能针对性的看某些问题发表一些我的的见解或者看法。其中回忆起来的问题以及常常园子讨论的问题包含以下:【可能想的不全后面能够更新】
一、EF的数据库上下文实例的生命周期管理的标准实践 ?
二、EF的数据库链接打开和关闭的时间点和管理是怎么回事 ?
三、EF为什么第一次启动这么慢和怎么解决 ?
四、EF在批量操做时对象跟踪时性能问题该如何解决 ?
五、EF批量数据时怎么提升速度和保证事务 ?
六、EF对复杂的查询表达式解析能力如何?
七、开发者怎么审查EF翻译的SQL语句?
八、开发者怎么监控EF在网站运行状况?
九、......
等等,可能问题还有不少这里暂时没有收录。接下来就是须要咱们逐一对问题思考和解析,注意:可能这里的理解有主观意识,固然某些问题我也会用实验例子来证明大体的论述。sql
分析:因为ef的读写操做都是基于DbContext数据库上下文来操做的,因此当进行这些操做的时候,就须要一个对象实例才能够,那么问题就在于我能够在同一个对象操做屡次吗,何时该建立这个对象以及何时结束它,这个对象存在多个有什么影响吗,对象实现了IDisposable接口我必需要在using中使用吗,好,这里我假设咱们是处于web应用程序中(一般状况也是如此),基于http请求来讨论这个问题
解答:
(a)一个(同一个)对象自己就是能够屡次链接访问数据库的包含了读写可能会屡次打开和关闭数据库链接,固然这里是基于ado.net数据库链接池的无须过于担忧,因此屡次操做自己就是合情合理的,也是必须的
数据库
(b) 建立对象的时刻问题,上下文得知,基于http请求的话,每次请求可能会有数据库访问的需求(大体都有这个可能),那么每次请求何时须要建立一个DbContext对象呢,其实在你的程序结构中,无非就是在Controller或者Service或者Repository中包含(或者说成注入更恰当)DbContext对象的自己被实例化的时候建立DbContext,结束无非也就是一、在包裹类释放时跟着释放(多实例模式)二、在请求结束的时候释放(请求期间共享实例模式)缓存
这里说点题外话:一般意义上来讲,把这些这些对象一般由Ioc容器统一(例如: Unity Autofac Ninject等等)来建立和管理生命周期这样来得更合适些,拿unity举例说明(固然这里也能够做为一个私有字段存在于你的Repositotry或者Service中都看你本身选择):这里咱们能够自定义个IDbContext接口让DbContext实现它这样这是为了方便注入
container.RegisterType<IDbContext, DJLNETDBContext>(new PerRequestLifetimeManager());
,而后设置为 PerRepuestLifetimeManager每次请求生命周期(基于HttpModule来实现的)Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
,来实现了请求单例,其余框架一样具有相似功能,相似asp.net core中AddScoped的功能。这样作的好处在于在一个请求期间就能够共享一个DbContext对象啦,既可让局面变得清晰和简单,又能够减小堆资源的消耗安全
(c)多个DbContext对象对于asp.net自己就是基于并发应用程序而言这种就是合理,且多个对象在不一样的请求线程中也是相互不受干扰和影响的,可能也就是对于同一时刻并发请求数过多对于内存占用可能稍有提高,这不是你须要担忧的问题,由于会释放的。
(d)IDisposable在我理解看来是提供给须要释放非托管资源的对象实现的,因此想固然觉得DbContext在实现中有这种操做,经过翻源码和多方做证得知,并不是如此(这里提早说明一下 一、使用using是由于一来为了准许一种契约或者模式二来为了保证安全性 二、至于connection的管理是DbContext本身的事情会自动处理好),详情请看问题2中有相应解析,因此不须要using已然可使用它完成你应有的操做。
并发
总结: 若是是EF,建议将其数据库实例上下文DbContext设置为请求期间共享一个实例对象保证建立和销毁操做保证按照您的预期执行,至于实现看本身喜爱拉,不过交给一些现成的ioc框架去实现,不乏是一个不错的选择!app
思考:若是是你怎么设计关于数据库链接对象的管理(这里的管理是链接对象的打开和关闭,并非建立和销毁,毕竟close和dispose仍是有区别的)更加合适呢?
解析: 首先上面1的的(d)也提到了关于using的问题一样也涉及connection,先让我来看一篇老外的博文:http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext/可能博文有点老了,可是在 Diego Vega回信中咱们发现关键信息(加粗标记关键信息):
The default behavior of DbContext is that the underlying connection is automatically opened any time is needed and closed when it is no longer needed. E.g. when you execute a query and iterate over query results using “foreach”, the call to IEnumerable
.GetEnumerator() will cause the connection to be opened , and when later there are no more results available, “foreach” will take care of calling Dispose on the enumerator, which will close the connection. In a similar way, a call to DbContext.SaveChanges() will open the connection before sending changes to the database and will close it before returning.
Given this default behavior, in many real-world cases it is harmless[无害的] to leave the context without disposing it and just rely on garbage collection.
That said, there are two main reason our sample code tends to always use “using” or dispose the context in some other way:
总结: EF自身已经自动的去控制链接对象的开关了,也就是当你去迭代查询对象或者保存修改时会在恰当的时候(具体看上文的时间点黑体部分)帮你打开或者关闭链接,这样就算不明显去调用dispose,让GC去管理DbContext的剩余托管资源也是无害,至于后半段只是解释下为什么仍是要实现IDispose接口以及存在的必要和安全性,其实做为编码的咱们是能够作到避免手动去控制链接对象,using只是做为最后的一道屏障而已。鉴于不能仅凭一片博文定论,能够参考一下源码中dispose中的实现:https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/DbContext.cs
解析: 来先看看关于这个问题老外反馈的issues(包含老外的作的实验):https://github.com/aspnet/EntityFramework/issues/4372
以及dudu园长解释的缘由所在以及解决方案:http://www.cnblogs.com/dudu/p/entity-framework-warm-up.html 在备注一下从IIS入手的如何优化 EF的博文地址:http://www.cnblogs.com/lkd3063601/p/4713637.html和从GAC入手优化的 https://www.fusonic.net/en/blog/3-steps-for-fast-entityframework-6.1-code-first-startup-performance/
总结 总的来讲这里都是搬运工拉,记录备注一下,固然EF这样设计也是合乎情理,在第一次访问以后缓存下来映射视图和一些元数据相关的东西,之后直接复用
分析: 这里先说下对象跟踪,默认EF是启动对象跟踪的,也就是context.Configuration.AutoDetectChangesEnabled = true;
当发现使用查询出来的对象属性值变动以后,在DbContext.SaveChanges
的时候这里会主动触发去检查对象的CurrentValues与OriginalValues的差别而后标记为Modified状态,当咱们有成百上千的对象须要去修改或者添加,对应 AddRang 或者 RemoveRang 的时候就会触发不少次对象跟踪,因此这是一个坏操做,参考https://msdn.microsoft.com/en-us/library/jj556205(v=vs.113).aspx官方示例作法以下:
using (var context = new BloggingContext()) { try { context.Configuration.AutoDetectChangesEnabled = false; // Make many calls in a loop foreach (var blog in aLotOfBlogs) { context.Blogs.Add(blog); } context.SaveChanges(); } finally { context.Configuration.AutoDetectChangesEnabled = true; } }
注意这里有个官方提示:Don’t forget to re-enable detection of changes after the loop — We've used a try/finally to ensure it is always re-enabled even if code in the loop throws an exception.
分析解答: 一直以来这就是一个焦点问题,问题也是在于为什么插入不少数据和修改不少数据的时候很慢,怎么解决这个问题以及还须要保持和原先逻辑(写逻辑)在一个事务里面等,固然你依然可以使用context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
这样的配置去提升代码层面的优化,这里的优化针对批量添加删除修改都是有效的,下面的System.Data.SqlClient.SqlBulkCopy
针对添加固然也指定了使用环境sql server毕竟是数据库自己支持才能够,不过这里的基本和EF无关了只是提供批量插入的一个途径而已。
那么加上事务是否能够快点呢?答案是确定的,使用Database.BeginTransaction()
或者System.Transactions.TransactionScope
将ef操做裹起来在SaveChanges以后,如若没发生异常打包提交在速度上面会有大幅度提高,相对于不显示使用事务机制,而使用EF默认事务机制的状况上面比较得出上面的结论,这个好处也得益于数据库自己的支持
除了批量添加对象能够忽略,可是我批量删除对象和批量修改对象则须要那拿到那些须要作此操做的源数据集合才能够,这里就须要先查询出来这些对象,而后遍历删除或者修改他们的属性,且还须要保持这些对象是被跟踪的对象,那么我能够不查询出来这些对象也想删除或者修改它们?
答案:能够的,其中基于目前的状况有两种作法,首先第一种也是我比较推荐的一种作法或者说是官方作法:https://msdn.microsoft.com/en-us/library/dn456843(v=vs.113).aspx
这里搬运一点官方代码(详情请参考官方代码):
using (var context = new BloggingContext()) { using (var dbContextTransaction = context.Database.BeginTransaction()) { try { context.Database.ExecuteSqlCommand( @"UPDATE Blogs SET Rating = 5" + " WHERE Name LIKE '%Entity Framework%'" ); var query = context.Posts.Where(p => p.Blog.Rating >= 5); foreach (var post in query) { post.Title += "[Cool Blog]"; } context.SaveChanges(); dbContextTransaction.Commit(); } catch (Exception) { dbContextTransaction.Rollback(); } } }
在不引入第三方框架的状况下,可以本身清晰的掌握代码以及处理方法,这是比较好的,而且可以和你想的处理结果一致,第二种作法: 即便用一些基于EF的第三方扩展,这里就展现了,由于自己对其不还不是很了解,其实我我的以为处理20%的需求可是又不想引入新的nuget包的状况,这样特殊处理也是能够的,再加上一点本身的封装例如:实现AOP层面的事务封装,这样在你ExecuteNonQuery();
与SaveChanges
就不会明显的感受到事务的存在从而把问题简化,抽到公共逻辑当中去
一 一,为什么在问题5以后就没了呀,关于后面
六、EF对复杂的查询表达式解析能力如何?
七、开发者怎么审查EF翻译的SQL语句?
八、开发者怎么监控EF在网站运行状况?
这几个问题将会在下一篇博文给出分析和解读,并非故意卖关子是博主自己尚未准备好下一篇文的内容哈,望各位原谅,关于以上的问题和解析及其回答属于我的意见,若有不对的地方欢迎讨论哈
划重点拉,顺便再补充一下示例代码
在下先干为敬:
不哭长夜者,不足以与人生。未曾为梦想奋斗,拿什么去燃烧青春。有梦之人亦终将老去,但少年心气如昨。