NET主流ORM框架分析
接上文我們測(cè)試了各個(gè)ORM框架的性能,大家可以很直觀的看到各個(gè)ORM框架與原生的ADO.NET在境刪改查的性能差異。這里和大家分享下我對(duì)ORM框架的理解及一些使用經(jīng)驗(yàn)。
ORM框架工作原理
所有的ORM框架的工作原理都離不開下面這張圖,只是每個(gè)框架的實(shí)現(xiàn)程度不同但是最終的目的是相同的。
如果是一個(gè)ORM框架那么一定會(huì)有上圖中藍(lán)色部分的這幾個(gè)元素,無(wú)論是增刪改查對(duì)于ORM一定是以對(duì)象為起點(diǎn),使用對(duì)象構(gòu)造出LINQ表達(dá)式,這樣我們?cè)趯?duì)象的世界中可以描述我們希望對(duì)數(shù)據(jù)庫(kù)所進(jìn)行的操作,LINQ的最終實(shí)現(xiàn)其實(shí)也是Lambda表達(dá)式(必盡LINQ在代碼上會(huì)直觀很多),功能較強(qiáng)的ORM中都會(huì)記錄有對(duì)象類型到數(shù)據(jù)庫(kù)對(duì)象的元數(shù)據(jù),使用這些元數(shù)據(jù)可以將復(fù)雜的Lambda表達(dá)式翻譯成一個(gè)通用的中間表達(dá)式,這個(gè)表達(dá)式其實(shí)是抽象于各個(gè)不同數(shù)據(jù)庫(kù)的具體實(shí)現(xiàn),最后中間表達(dá)式再按指定數(shù)據(jù)庫(kù)的具體實(shí)現(xiàn)生成最終的SQL語(yǔ)句,交由ADO.NET對(duì)象執(zhí)行到數(shù)據(jù)庫(kù),如果數(shù)據(jù)存在返回則會(huì)回寫到CLR對(duì)象中。
以上概括了所有ORM的實(shí)現(xiàn)過(guò)程,這里比較典型的是Dapper,它之所以小巧而高性能的原因正是因?yàn)樵摽蚣苤袥]有綠色的部分,可以說(shuō)是所有ORM框架中最為精簡(jiǎn)的(可能還會(huì)存在其他類似的框架,這里僅用Dapper作為典型案例說(shuō)明),因此沒有多余的部分自然小而快。然而綠色部分的存在就決定了該ORM框架的功能到底有多強(qiáng),剛好目前世面上的ORM框架中只有EF是完全實(shí)現(xiàn)了綠色框中的各個(gè)元素,并且支持最為全面,所以EF必然重而慢(至少EF6相對(duì)其他框架是比較差的)。
典型ORM框架實(shí)現(xiàn)
?Dapper:需要自己手寫SQL語(yǔ)句完成操作,比較簡(jiǎn)單直接不過(guò)還有DapperExtensions的幫助,可以在插入、修改及刪除時(shí)不用寫SQL語(yǔ)句全對(duì)象操作,這里需要指出的是它應(yīng)該不能算是一個(gè)完全的ORM框架,因?yàn)槿粘i_發(fā)占比多的查詢還是需要手寫SQL,無(wú)法對(duì)象類型化。
Nhibernate:這是個(gè)比較久遠(yuǎn)的框架,該框架需要自己配置元數(shù)據(jù)功能上會(huì)比DapperExtensions強(qiáng)一點(diǎn),不過(guò)對(duì)于查詢表達(dá)式?jīng)]有太好的支持。
SqlSugar,Chloe.ORM:此類框架雖然不能支持LINQ但是可以實(shí)現(xiàn)自己一套Lambda表達(dá)式用于生成SQL語(yǔ)句,總之其總體思想就是把SQL語(yǔ)法轉(zhuǎn)換成表達(dá)式語(yǔ)法,比較典型的是會(huì)用表達(dá)式中的變量名用作生成SQL語(yǔ)句中的別名,而且附加功能會(huì)比較多,總之比較靈活實(shí)用。
Linq2db:此類ORM算是實(shí)現(xiàn)的比較完整的ORM流程(見上圖),而且支持眾多的數(shù)據(jù)庫(kù),總之功能算是比較強(qiáng)大了。
EF功能最強(qiáng)的ORM
?EF是目前為止功能最強(qiáng)的ORM,這個(gè)相信大家沒有什么爭(zhēng)議,大家可以參考一下這份文檔 (EF Core and EF6 Feature by Feature Comparison),其中列出了EF6與EFCore的功能特性,相信沒有哪個(gè)ORM框架實(shí)現(xiàn)了其中大部分的特性,下面隨便列舉幾個(gè)特性:
- 全面支持LINQ查詢,有不少框架也會(huì)支持LINQ可是使用時(shí)會(huì)發(fā)現(xiàn)有些寫法是不支持的,但是EF是目前支持最全的,在截止到目前為止EFCore的正式版本中也沒有完全實(shí)現(xiàn)EF6對(duì)LINQ的支持。 
- EFCore性能提升:從上一遍博客(Mego(1))中可以看出對(duì)于個(gè)功能強(qiáng)大的ORM框架而言EFCore已提升相當(dāng)多了總之已非常接近原生的ADO.NET框架了。 
- 對(duì)象繼承:這是個(gè)很好的設(shè)計(jì),它可以讓你將關(guān)系數(shù)據(jù)庫(kù)實(shí)現(xiàn)對(duì)象繼承化,在數(shù)據(jù)表中也會(huì)存在等價(jià)父子表。 
- 數(shù)據(jù)庫(kù)遷移功能,無(wú)論你怎么看待這個(gè)功能,但是對(duì)于開發(fā)而言這個(gè)功能太實(shí)用了,相信大家在很多DEMO中可以見到使用EF生成數(shù)據(jù)庫(kù)。 
- 對(duì)象的集合屬性展開,通常我們?cè)谌∮唵螘r(shí)也希望一起把明細(xì)也取出來(lái),更多的情況是還可以把訂單數(shù)據(jù)分頁(yè),這個(gè)功能在大部ORM都是沒有的需要自己處理,然后在EF中只需要寫一行代碼就能搞定。 
- 多對(duì)多關(guān)系:這個(gè)也是個(gè)比較常用但是也在其他ORM比較少見的功能,EF也是目前支持最好的。 
- EF框架對(duì)接了微軟的眾多其他需要數(shù)據(jù)訪問(wèn)的框架,例如ASP.NET Identity,ASP.NET WebApi ODATA。 
EF與EFCore缺陷
?雖然EF或EFCore功能已經(jīng)很強(qiáng)大了,不過(guò)在開發(fā)過(guò)程中還有很多不足的地方,也有很多缺陷這也是導(dǎo)致有很多人使用之后而放棄的原因之一(當(dāng)然還有EF6的性能問(wèn)題)。下面將列一下EF6的不足給大家參考。
數(shù)據(jù)更新功能不足
在EF中數(shù)據(jù)提交的功能一直不行,在EF6中所有的數(shù)據(jù)操作都是單個(gè)對(duì)象分別提交了,這也就表示在大量數(shù)據(jù)提交過(guò)程中每個(gè)對(duì)象都要發(fā)送一次數(shù)據(jù)庫(kù),這也是導(dǎo)致為什么性能底下了,而且更新及刪除數(shù)據(jù)一定要從數(shù)據(jù)庫(kù)取回對(duì)象或附加對(duì)象才能操作。這個(gè)問(wèn)題在EFCore中都得到解決而且性能比較好如前文的測(cè)試可見。但是EF到目前都不能支持條件更新、條件刪除。不過(guò)這個(gè)問(wèn)題可以使用這個(gè)框架(Entity Framework Plus)解決,不過(guò)這個(gè)框架是收費(fèi)的。
創(chuàng)建時(shí)間修改時(shí)間問(wèn)題
?我們?cè)跇I(yè)務(wù)系統(tǒng)開發(fā)時(shí)常會(huì)遇到創(chuàng)建和更新數(shù)據(jù)時(shí)需要記錄創(chuàng)建時(shí)間或修改時(shí)間的情況,不過(guò)目前無(wú)論是EF或EFCore中都只能用觸發(fā)器來(lái)解決,不過(guò)當(dāng)數(shù)據(jù)表變多時(shí)維護(hù)這些觸發(fā)器也是個(gè)比較麻煩的事情。有人會(huì)想到用默認(rèn)值,這個(gè)EF中是支持的,但是EF不能選擇性的插入或更新指定的字段,這是個(gè)兩難的情況。
創(chuàng)建及修改時(shí)間只是這類問(wèn)題的一個(gè)例子,這類問(wèn)題可以概括為我們?cè)诓迦牖蚋聰?shù)據(jù)時(shí)希望指定字段是調(diào)用相應(yīng)的數(shù)據(jù)表達(dá)式生成的,而不是應(yīng)用程序的值,這里可以舉個(gè)實(shí)例給大家看,在一個(gè)負(fù)載均衡的架構(gòu)中我們需要在創(chuàng)建訂單時(shí)生成訂單流水號(hào),這個(gè)流水號(hào)一定要唯一且連續(xù),在這種多服務(wù)器并發(fā)的情況下只有在數(shù)據(jù)庫(kù)生成這個(gè)流水號(hào)才是比較好的選擇,這樣可以充分保證當(dāng)提交失敗時(shí)新分的流水號(hào)可以歸還,同時(shí)也保證大家依次生成不會(huì)重復(fù)且連續(xù),這個(gè)時(shí)候就很需要ORM可以自動(dòng)使用單號(hào)生成函數(shù)來(lái)生成值。
EF中的曲線解決,在EF6中插入、更改或刪除操作是支持存儲(chǔ)過(guò)程映射的,大家可以參考這個(gè)文章(Entity Framework Code First Insert, Update, and Delete Stored Procedures (EF6 onwards))里面有詳細(xì)的說(shuō)明,這里我們可以做文章,我們修改存儲(chǔ)過(guò)程改為調(diào)用我們指定的表達(dá)式即可,這樣可以不用觸發(fā)器,也能達(dá)到目的。這里有人會(huì)問(wèn)維護(hù)存儲(chǔ)也需要比較大代價(jià),這里我們可以重寫EF的數(shù)據(jù)庫(kù)遷移生成存儲(chǔ)過(guò)程的代碼來(lái)統(tǒng)一處理。
實(shí)體集合屬性多層展開
?無(wú)論是EF或EFCore中有一個(gè)Include函數(shù),用來(lái)在返回實(shí)體對(duì)象時(shí)顯示包含它的集合或?qū)ο髮傩?#xff0c;這個(gè)操作被稱為屬性展開,這里先這么稱呼。不過(guò)可以展開對(duì)象屬性(例如訂單 -> 客戶?-> 負(fù)責(zé)人?-> 公司),也可以展開集合屬性(例如 訂單?-> 明細(xì)集合),這時(shí)集合下面的屬性就不能展開了例如我希望得到(例如 訂單?-> 明細(xì)集合?-> 產(chǎn)品)這是不支持的。
補(bǔ)充EF實(shí)際內(nèi)部代碼是可以支持這種操作但是沒公開,如果有對(duì)ASP.NET WebApi OData有所了解的朋友可以知道,如果你的OData服務(wù)是建立在EF基礎(chǔ)之上的,在ODATA語(yǔ)法中可以支持多級(jí)數(shù)據(jù)展開的,并且在EF6中得到很好的實(shí)現(xiàn),不過(guò)這些的前提是你的數(shù)據(jù)庫(kù)是SQL Server。
對(duì)象關(guān)系限制
例如一個(gè)訂單會(huì)有多個(gè)明細(xì),每個(gè)明細(xì)都會(huì)對(duì)應(yīng)一個(gè)產(chǎn)品,從邏輯上我們就可以認(rèn)為訂單和產(chǎn)品是多對(duì)多關(guān)系了,但是在EF中就沒有這么自由的可以操作了。
在EF中關(guān)系的操作有很多限制,這個(gè)特別在多對(duì)多關(guān)系中很嚴(yán)重,EF6中會(huì)生成一個(gè)隱藏的關(guān)系實(shí)體用來(lái)建立關(guān)聯(lián),這些都是你不能控制的,而且在關(guān)系鏈接中要求主表一定要是主鍵,不過(guò)在EFCore中這些問(wèn)題得到一些解決,不過(guò)EFCore目前不能支持多對(duì)多關(guān)系,至少目前的Flush API中還沒有看到。
時(shí)間戳
感覺EF中不少特性都是為SQL Server設(shè)計(jì)的,EF中強(qiáng)制時(shí)間戳必須CLR屬性是字節(jié)數(shù)組,不過(guò)在MySQL中的時(shí)間戳其實(shí)就是日期時(shí)間。
多數(shù)據(jù)庫(kù)實(shí)現(xiàn)差異
EF和EFCore是支持多個(gè)數(shù)據(jù)庫(kù),其原理是EF定義的前文所說(shuō)的中間表達(dá)式,然后再交給各方自行實(shí)現(xiàn)后續(xù)的操作。這種設(shè)計(jì)有如下缺點(diǎn):
- 數(shù)據(jù)庫(kù)支持綁定了數(shù)據(jù)訪問(wèn)組件,例如MySQL.Data.Entity這個(gè)組件必須需要在MySQL.Data組件下工作,這時(shí)如果你想換個(gè)訪問(wèn)MySQL的組件是不可以的。 
- 數(shù)據(jù)庫(kù)提供程序?qū)崿F(xiàn)代價(jià)太大,如果大家有時(shí)間可以去看下EntityFramework.SqlServer或MySQL.Data.Entity里的實(shí)現(xiàn)代碼,這些都是著名公司微軟和Oracle維護(hù)的代碼,其中的實(shí)現(xiàn)代碼非常復(fù)雜,對(duì)于一般的團(tuán)隊(duì)而言可以算是一個(gè)非常大的項(xiàng)目。 
- 數(shù)據(jù)庫(kù)提供程序?qū)崿F(xiàn)質(zhì)量,還是以MySQL.Data.Entity為例子,每個(gè)提供程序并不像微軟那樣完全實(shí)現(xiàn)EF在SQL Server中的各個(gè)功能,而且代碼非常復(fù)雜問(wèn)題會(huì)很多,即使是Oracle所維護(hù)的代碼也是同樣糟糕,下面分享MySQL.Data.Entity的兩個(gè)例子。 
MySQL.Data.Entity問(wèn)題一:這個(gè)組件在MySQL5.6由于數(shù)據(jù)庫(kù)的主鍵限制在700多個(gè)字節(jié),因此它在自動(dòng)遷移數(shù)據(jù)庫(kù)操作時(shí)會(huì)報(bào)錯(cuò)主鍵過(guò)長(zhǎng)錯(cuò)誤,等于這個(gè)是完全不能用的。
MySQL.Data.Entity問(wèn)題二:這個(gè)組件在MySQL5.7中,由于數(shù)據(jù)庫(kù)的BUG(虛擬列如"SELECT 1"的IS NULL判斷錯(cuò)誤問(wèn)題)會(huì)導(dǎo)致特定的LINQ表達(dá)式生成的SQL腳本是不能得到正確結(jié)果的。
MySQL.Data.Entity問(wèn)題三:對(duì)于繼承的SQL生成這一部分是完全沒有實(shí)現(xiàn)的,你會(huì)發(fā)現(xiàn)所生成的腳本都是錯(cuò)的。
MySQL.Data.Entity問(wèn)題四:EF的Include操作的具體實(shí)現(xiàn)是依賴于CROSS APPLY(SQL Server)語(yǔ)法的,不過(guò)MySQL中完全沒有,也很寫出替代語(yǔ)句,因此這個(gè)功能在MySQL下等于是不存在的。
以上是因?yàn)槲覅⒓恿薊F對(duì)接MySQL的改造項(xiàng)目,這是我和我的團(tuán)隊(duì)折騰了幾個(gè)月才得出的一些總結(jié),不過(guò)好在我們最后通過(guò)修改MySQL.Data.Entity源碼解決了這些問(wèn)題。
總之其他框架不能確定,但是在EF中各個(gè)數(shù)據(jù)庫(kù)的支持程度是很不對(duì)稱的。
總結(jié)
在EntityFramework長(zhǎng)達(dá)10年的發(fā)展歷程中,開始發(fā)展很快,但是后面到EntityFramework6.1.3(2015年3月)這個(gè)版本時(shí)就好像是EntityFramework的結(jié)束,之后EntityFramework6就再?zèng)]有此實(shí)制上的更新了直到今天,應(yīng)該是微軟已經(jīng)放棄它轉(zhuǎn)向EntityFrameworkCore上,不過(guò)EntityFrameworkCore的發(fā)展也沒有這么快,至今還沒有超過(guò)EntityFramework6,所以直到今天微軟都不敢對(duì)外宣布放棄EntityFramework6。
?
以上是我的個(gè)人見解,僅供參考。
原文地址?http://www.cnblogs.com/CarefreeXT/p/8729085.html
.NET社區(qū)新聞,深度好文,歡迎訪問(wèn)公眾號(hào)文章匯總 http://www.csharpkit.com 
總結(jié)
以上是生活随笔為你收集整理的NET主流ORM框架分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: Hangfire使用Applicatio
- 下一篇: 谈谈ASP.NET Core中的Resp
