【EntityFramework 6.1.3】个人理解与问题记录
前言
又是一個(gè)炎熱夏日的晚上,開(kāi)著空調(diào)聽(tīng)著音樂(lè)又開(kāi)始了我們今天的博文。此文并不是ROM工具哪家強(qiáng)之類的引戰(zhàn)貼,只是本文自己的一點(diǎn)看法和見(jiàn)解,望前輩看官有望斧正
聲明
本文歡迎轉(zhuǎn)載,原文地址:http://www.cnblogs.com/DjlNet/p/7220720.html
開(kāi)始正文
話說(shuō)回顧歷史的話,在linq to sql的年代到后面linq to entity也就是ef4.1以至于現(xiàn)在的ef6.1.3歷經(jīng)了好些歲月的打磨,且也用ef6在真實(shí)項(xiàng)目中使用體會(huì)到了在開(kāi)發(fā)速度和維護(hù)成本體現(xiàn)出來(lái)的優(yōu)勢(shì),當(dāng)然并不代表它就是沒(méi)有弱點(diǎn)或者是說(shuō)要通過(guò)一些方式去規(guī)避不當(dāng)使用造成的問(wèn)題哈,所以一個(gè)東西不可能做到盡善盡美,畢竟在我看來(lái)它也只是基于C#3開(kāi)始帶來(lái)linq基礎(chǔ)的上面的一層數(shù)據(jù)庫(kù)訪問(wèn)的抽象,當(dāng)然不光是查詢啦,也包含寫(xiě)操作啦或者事務(wù)之類的,反正啦上層抽象只能做到它自己功能的抽取也就是包含關(guān)系型數(shù)據(jù)庫(kù)大家都有的一些特性和操作,這樣的抽象也是合情合理所以就在linq的基礎(chǔ)上面就這樣孕育而生,以及具體的話就需要交給數(shù)據(jù)庫(kù)對(duì)應(yīng)的提供器去解析表達(dá)式去適配數(shù)據(jù)庫(kù)自個(gè)兒的一些特點(diǎn)和異同,這也是復(fù)雜和小心的過(guò)程因?yàn)檫@基本都關(guān)系著到達(dá)數(shù)據(jù)庫(kù)執(zhí)行的sql語(yǔ)句到底如何的問(wèn)題,實(shí)際反應(yīng)的就是產(chǎn)生的執(zhí)行計(jì)劃消耗等等,這其中也是其他ORM工具(暫且這樣稱呼吧,例如一些Dapper+Extension之類說(shuō)實(shí)話有點(diǎn)勉強(qiáng),注意這里并沒(méi)有故意貶低牛逼的Dapper的意思,畢竟我大Stackoverflow就是用的它,具體可以去github查看wiki)同樣需要去做的一件事兒,當(dāng)然這里不排除微軟老爹對(duì)自家數(shù)據(jù)庫(kù)sql server親兒子有些小操作。
這里要額外提一下:感謝@Pomelo大大對(duì)MySql實(shí)現(xiàn)ef core驅(qū)動(dòng)的奉獻(xiàn),也得到了微軟的支持,所以應(yīng)該可以放心食用
,微軟官方目前的數(shù)據(jù)庫(kù)驅(qū)動(dòng)列表:https://docs.microsoft.com/zh-cn/ef/core/providers/,其中ef core對(duì)于sql server有個(gè)批量操作的優(yōu)化,偶爾看看issues得知,同時(shí)發(fā)現(xiàn)同事園子一老哥也發(fā)文說(shuō)明了情況,具體傳送門(mén):https://github.com/aspnet/EntityFramework/issues/9270,這個(gè)貌似是一直被業(yè)界所詬病的問(wèn)題,當(dāng)然也可以自定義擴(kuò)展或者特殊情況配合sql+transaction來(lái)處理批量寫(xiě)問(wèn)題我覺(jué)得也是可以的,以至于一些第三方ef batch框架(例如:entity framework plus)這里暫不評(píng)價(jià)了,個(gè)人覺(jué)得畢竟帶來(lái)方便的同時(shí)引入了復(fù)雜度
好了屁話說(shuō)了一大推,說(shuō)到這里你們肯定會(huì)以為又是一個(gè)EF長(zhǎng)篇大論文入門(mén)文,例如什么是ORM,什么是OTO,什么是Code First,這些介紹的太多太多了,建議還是才看微軟ef官方文檔即可(https://msdn.microsoft.com/en-us/library/ee712907(v=vs.113).aspx)以及微軟新上線的文檔地址:https://docs.microsoft.com/en-us/ef/,也許能把官方文檔大致瀏覽一邊基本做到心中有數(shù)遇到問(wèn)題再去翻翻即可,比我在這里BB可能或許有用,所以這里并不會(huì)對(duì)EF有一個(gè)詳細(xì)的解讀(當(dāng)然做到詳細(xì)解析,我也做到不到呀),這方面的博文已經(jīng)數(shù)不勝數(shù),在這里可能針對(duì)性的看某些問(wèn)題發(fā)表一些個(gè)人的看法或者見(jiàn)解。其中回憶起來(lái)的問(wèn)題以及經(jīng)常園子討論的問(wèn)題包含如下:【可能想的不全后面可以更新】
1、EF的數(shù)據(jù)庫(kù)上下文實(shí)例的生命周期管理的標(biāo)準(zhǔn)實(shí)踐 ?
2、EF的數(shù)據(jù)庫(kù)連接打開(kāi)和關(guān)閉的時(shí)間點(diǎn)和管理是怎么回事 ?
3、EF為何第一次啟動(dòng)這么慢和怎么解決 ?
4、EF在批量操作時(shí)對(duì)象跟蹤時(shí)性能問(wèn)題該如何解決 ?
5、EF批量數(shù)據(jù)時(shí)怎么提高速度和保證事務(wù) ?
6、EF對(duì)復(fù)雜的查詢表達(dá)式解析能力如何?
7、開(kāi)發(fā)者怎么審查EF翻譯的SQL語(yǔ)句?
8、開(kāi)發(fā)者怎么監(jiān)控EF在網(wǎng)站運(yùn)行情況?
9、......
等等,可能問(wèn)題還有很多這里暫時(shí)沒(méi)有收錄。接下來(lái)就是需要我們逐一對(duì)問(wèn)題思考和解析,注意:可能這里的理解有主觀意識(shí),當(dāng)然某些問(wèn)題我也會(huì)用實(shí)驗(yàn)例子來(lái)證實(shí)大致的論述。
1、EF的數(shù)據(jù)庫(kù)上下文實(shí)例的生命周期管理的標(biāo)準(zhǔn)實(shí)踐 ?
分析:由于ef的讀寫(xiě)操作都是基于DbContext數(shù)據(jù)庫(kù)上下文來(lái)操作的,所以當(dāng)進(jìn)行這些操作的時(shí)候,就需要一個(gè)對(duì)象實(shí)例才可以,那么問(wèn)題就在于我可以在同一個(gè)對(duì)象操作多次嗎,什么時(shí)候該創(chuàng)建這個(gè)對(duì)象以及什么時(shí)候結(jié)束它,這個(gè)對(duì)象存在多個(gè)有什么影響嗎,對(duì)象實(shí)現(xiàn)了IDisposable接口我必須要在using中使用嗎,好,這里我假設(shè)我們是處于web應(yīng)用程序中(通常情況也是如此),基于http請(qǐng)求來(lái)討論這個(gè)問(wèn)題
解答:
(a)一個(gè)(同一個(gè))對(duì)象本身就是可以多次連接訪問(wèn)數(shù)據(jù)庫(kù)的包含了讀寫(xiě)可能會(huì)多次打開(kāi)和關(guān)閉數(shù)據(jù)庫(kù)連接,當(dāng)然這里是基于ado.net數(shù)據(jù)庫(kù)連接池的無(wú)須過(guò)于擔(dān)心,所以多次操作本身就是合情合理的,也是必須的
(b) 創(chuàng)建對(duì)象的時(shí)刻問(wèn)題,上下文得知,基于http請(qǐng)求的話,每次請(qǐng)求可能會(huì)有數(shù)據(jù)庫(kù)訪問(wèn)的需求(大致都有這個(gè)可能),那么每次請(qǐng)求什么時(shí)候需要?jiǎng)?chuàng)建一個(gè)DbContext對(duì)象呢,其實(shí)在你的程序結(jié)構(gòu)中,無(wú)非就是在Controller或者Service或者Repository中包含(或者說(shuō)成注入更恰當(dāng))DbContext對(duì)象的本身被實(shí)例化的時(shí)候創(chuàng)建DbContext,結(jié)束無(wú)非也就是1、在包裹類釋放時(shí)跟著釋放(多實(shí)例模式)2、在請(qǐng)求結(jié)束的時(shí)候釋放(請(qǐng)求期間共享實(shí)例模式)
這里說(shuō)點(diǎn)題外話:通常意義上來(lái)說(shuō),把這些這些對(duì)象通常由Ioc容器統(tǒng)一(例如: Unity Autofac Ninject等等)來(lái)創(chuàng)建和管理生命周期這樣來(lái)得更合適些,拿unity舉例說(shuō)明(當(dāng)然這里也可以作為一個(gè)私有字段存在于你的Repositotry或者Service中都看你自己選擇):這里我們可以自定義個(gè)IDbContext接口讓DbContext實(shí)現(xiàn)它這樣這是為了方便注入
container.RegisterType<IDbContext, DJLNETDBContext>(new PerRequestLifetimeManager());,然后設(shè)置為 PerRepuestLifetimeManager每次請(qǐng)求生命周期(基于HttpModule來(lái)實(shí)現(xiàn)的)Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));,來(lái)實(shí)現(xiàn)了請(qǐng)求單例,其他框架同樣具備類似功能,類似asp.net core中AddScoped的功能。這樣做的好處在于在一個(gè)請(qǐng)求期間就可以共享一個(gè)DbContext對(duì)象啦,既可以讓局面變得清晰和簡(jiǎn)單,又可以減少堆資源的消耗
(c)多個(gè)DbContext對(duì)象對(duì)于asp.net本身就是基于并發(fā)應(yīng)用程序而言這種就是合理,且多個(gè)對(duì)象在不同的請(qǐng)求線程中也是相互不受干擾和影響的,可能也就是對(duì)于同一時(shí)刻并發(fā)請(qǐng)求數(shù)過(guò)多對(duì)于內(nèi)存占用可能稍有提升,這不是你需要擔(dān)心的問(wèn)題,因?yàn)闀?huì)釋放的。
(d)IDisposable在我理解看來(lái)是提供給需要釋放非托管資源的對(duì)象實(shí)現(xiàn)的,所以想當(dāng)然以為DbContext在實(shí)現(xiàn)中有這種操作,通過(guò)翻源碼和多方作證得知,并非如此(這里提前說(shuō)明一下 1、使用using是因?yàn)橐粊?lái)為了準(zhǔn)許一種契約或者模式二來(lái)為了保證安全性 2、至于connection的管理是DbContext自己的事情會(huì)自動(dòng)處理好),詳情請(qǐng)看問(wèn)題2中有相應(yīng)解析,所以不需要using已然可以使用它完成你應(yīng)有的操作。
總結(jié): 如果是EF,建議將其數(shù)據(jù)庫(kù)實(shí)例上下文DbContext設(shè)置為請(qǐng)求期間共享一個(gè)實(shí)例對(duì)象保證創(chuàng)建和銷(xiāo)毀操作保證按照您的預(yù)期執(zhí)行,至于實(shí)現(xiàn)看自己喜好拉,不過(guò)交給一些現(xiàn)成的ioc框架去實(shí)現(xiàn),不乏是一個(gè)不錯(cuò)的選擇!
2、EF的數(shù)據(jù)庫(kù)連接打開(kāi)和關(guān)閉的時(shí)間點(diǎn)和管理是怎么回事 ?
思考:如果是你怎么設(shè)計(jì)關(guān)于數(shù)據(jù)庫(kù)連接對(duì)象的管理(這里的管理是連接對(duì)象的打開(kāi)和關(guān)閉,并不是創(chuàng)建和銷(xiāo)毀,畢竟close和dispose還是有區(qū)別的)更加合適呢?
解析: 首先上面1的的(d)也提到了關(guān)于using的問(wèn)題同樣也涉及connection,先讓我來(lái)看一篇老外的博文:http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext/可能博文有點(diǎn)老了,但是在 Diego Vega回信中我們發(fā)現(xiàn)關(guān)鍵信息(加粗標(biāo)記關(guān)鍵信息):
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[無(wú)害的] 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:
By the way, with DbContext the pattern to open the connection manually and override the automatic open/close behavior is a bit awkward:
((IObjectContextAdapter)dbContext).ObjectContext.Connection.Open()
But we have a bug to make this easier as it used to be with ObjectContext before, e.g.:
dbContext.Database.Connection.Open()
總結(jié): EF自身已經(jīng)自動(dòng)的去控制連接對(duì)象的開(kāi)關(guān)了,也就是當(dāng)你去迭代查詢對(duì)象或者保存修改時(shí)會(huì)在恰當(dāng)?shù)臅r(shí)候(具體看上文的時(shí)間點(diǎn)黑體部分)幫你打開(kāi)或者關(guān)閉連接,這樣就算不明顯去調(diào)用dispose,讓GC去管理DbContext的剩余托管資源也是無(wú)害,至于后半段只是解釋下為何還是要實(shí)現(xiàn)IDispose接口以及存在的必要和安全性,其實(shí)作為編碼的我們是可以做到避免手動(dòng)去控制連接對(duì)象,using只是作為最后的一道屏障而已。鑒于不能僅憑一片博文定論,可以參考一下源碼中dispose中的實(shí)現(xiàn):https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/DbContext.cs
3、EF為何第一次啟動(dòng)這么慢和怎么解決 ?
解析: 來(lái)先看看關(guān)于這個(gè)問(wèn)題老外反饋的issues(包含老外的做的實(shí)驗(yàn)):https://github.com/aspnet/EntityFramework/issues/4372
以及dudu園長(zhǎng)解釋的原因所在以及解決方案:http://www.cnblogs.com/dudu/p/entity-framework-warm-up.html 在備注一下從IIS入手的如何優(yōu)化 EF的博文地址:http://www.cnblogs.com/lkd3063601/p/4713637.html和從GAC入手優(yōu)化的 https://www.fusonic.net/en/blog/3-steps-for-fast-entityframework-6.1-code-first-startup-performance/
總結(jié) 總的來(lái)說(shuō)這里都是搬運(yùn)工拉,記錄備注一下,當(dāng)然EF這樣設(shè)計(jì)也是合乎情理,在第一次訪問(wèn)之后緩存下來(lái)映射視圖和一些元數(shù)據(jù)相關(guān)的東西,以后直接復(fù)用
4、EF在批量操作時(shí)對(duì)象跟蹤時(shí)性能問(wèn)題該如何解決 ?
分析: 這里先說(shuō)下對(duì)象跟蹤,默認(rèn)EF是啟動(dòng)對(duì)象跟蹤的,也就是context.Configuration.AutoDetectChangesEnabled = true; 當(dāng)發(fā)現(xiàn)使用查詢出來(lái)的對(duì)象屬性值變更之后,在DbContext.SaveChanges的時(shí)候這里會(huì)主動(dòng)觸發(fā)去檢查對(duì)象的CurrentValues與OriginalValues的差異然后標(biāo)記為Modified狀態(tài),當(dāng)我們有成百上千的對(duì)象需要去修改或者添加,對(duì)應(yīng) AddRang 或者 RemoveRang 的時(shí)候就會(huì)觸發(fā)很多次對(duì)象跟蹤,所以這是一個(gè)壞操作,參考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; } }注意這里有個(gè)官方提示: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.
5、EF批量數(shù)據(jù)時(shí)怎么提高速度和保證事務(wù) ?
分析解答: 一直以來(lái)這就是一個(gè)焦點(diǎn)問(wèn)題,問(wèn)題也是在于為何插入很多數(shù)據(jù)和修改很多數(shù)據(jù)的時(shí)候很慢,怎么解決這個(gè)問(wèn)題以及還需要保持和原先邏輯(寫(xiě)邏輯)在一個(gè)事務(wù)里面等,當(dāng)然你依然可使用context.Configuration.AutoDetectChangesEnabled = false;context.Configuration.ValidateOnSaveEnabled = false;這樣的配置去提高代碼層面的優(yōu)化,這里的優(yōu)化針對(duì)批量添加刪除修改都是有效的,下面的System.Data.SqlClient.SqlBulkCopy針對(duì)添加當(dāng)然也指定了使用環(huán)境sql server畢竟是數(shù)據(jù)庫(kù)本身支持才可以,不過(guò)這里的基本和EF無(wú)關(guān)了只是提供批量插入的一個(gè)途徑而已。
那么加上事務(wù)是否可以快點(diǎn)呢?答案是肯定的,使用Database.BeginTransaction()或者System.Transactions.TransactionScope將ef操作裹起來(lái)在SaveChanges之后,如若沒(méi)發(fā)生異常打包提交在速度上面會(huì)有大幅度提升,相對(duì)于不顯示使用事務(wù)機(jī)制,而使用EF默認(rèn)事務(wù)機(jī)制的情況上面比較得出上面的結(jié)論,這個(gè)好處也得益于數(shù)據(jù)庫(kù)本身的支持
除了批量添加對(duì)象可以忽略,但是我批量刪除對(duì)象和批量修改對(duì)象則需要那拿到那些需要做此操作的源數(shù)據(jù)集合才可以,這里就需要先查詢出來(lái)這些對(duì)象,然后遍歷刪除或者修改他們的屬性,且還需要保持這些對(duì)象是被跟蹤的對(duì)象,那么我可以不查詢出來(lái)這些對(duì)象也想刪除或者修改它們?
答案:可以的,其中基于目前的情況有兩種做法,首先第一種也是我比較推薦的一種做法或者說(shuō)是官方做法:https://msdn.microsoft.com/en-us/library/dn456843(v=vs.113).aspx
這里搬運(yùn)一點(diǎn)官方代碼(詳情請(qǐng)參考官方代碼):
在不引入第三方框架的情況下,能夠自己清晰的掌握代碼以及處理方法,這是比較好的,并且能夠和你想的處理結(jié)果一致,第二種做法: 即使用一些基于EF的第三方擴(kuò)展,這里就展示了,因?yàn)楸旧韺?duì)其不還不是很了解,其實(shí)我個(gè)人覺(jué)得處理20%的需求但是又不想引入新的nuget包的情況,這樣特殊處理也是可以的,再加上一點(diǎn)自己的封裝例如:實(shí)現(xiàn)AOP層面的事務(wù)封裝,這樣在你ExecuteNonQuery(); 與SaveChanges就不會(huì)明顯的感覺(jué)到事務(wù)的存在從而把問(wèn)題簡(jiǎn)化,抽到公共邏輯當(dāng)中去
總結(jié)與后續(xù)
一 一,為何在問(wèn)題5之后就沒(méi)了呀,關(guān)于后面
6、EF對(duì)復(fù)雜的查詢表達(dá)式解析能力如何?
7、開(kāi)發(fā)者怎么審查EF翻譯的SQL語(yǔ)句?
8、開(kāi)發(fā)者怎么監(jiān)控EF在網(wǎng)站運(yùn)行情況?
這幾個(gè)問(wèn)題將會(huì)在下一篇博文給出分析和解讀,并不是故意賣(mài)關(guān)子是博主本身還沒(méi)有準(zhǔn)備好下一篇文的內(nèi)容哈,望各位原諒,關(guān)于以上的問(wèn)題和解析及其回答屬于個(gè)人意見(jiàn),如有不對(duì)的地方歡迎討論哈
【2017年8月3日21:50:50】
劃重點(diǎn)拉,順便再補(bǔ)充一下示例代碼
后記
在下先干為敬:
不哭長(zhǎng)夜者,不足以與人生。不曾為夢(mèng)想奮斗,拿什么去燃燒青春。有夢(mèng)之人亦終將老去,但少年心氣如昨。
轉(zhuǎn)載于:https://www.cnblogs.com/DjlNet/p/7220720.html
總結(jié)
以上是生活随笔為你收集整理的【EntityFramework 6.1.3】个人理解与问题记录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: php 二维数组 根据某个字段排序
- 下一篇: Storm架构和编程模型总结