1. 程式人生 > >NET主流ORM框架分析

NET主流ORM框架分析

接上文我們測試了各個ORM框架的效能,大家可以很直觀的看到各個ORM框架與原生的ADO.NET在境刪改查的效能差異。這裡和大家分享下我對ORM框架的理解及一些使用經驗。

ORM框架工作原理

所有的ORM框架的工作原理都離不開下面這張圖,只是每個框架的實現程度不同但是最終的目的是相同的。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

如果是一個ORM框架那麼一定會有上圖中藍色部分的這幾個元素,無論是增刪改查對於ORM一定是以物件為起點,使用物件構造出LINQ表示式,這樣我們在物件的世界中可以描述我們希望對資料庫所進行的操作,LINQ的最終實現其實也是Lambda表示式(必盡LINQ在程式碼上會直觀很多),功能較強的ORM中都會記錄有物件型別到資料庫物件的元資料,使用這些元資料可以將複雜的Lambda表示式翻譯成一個通用的中間表示式,這個表示式其實是抽象於各個不同資料庫的具體實現,最後中間表示式再按指定資料庫的具體實現生成最終的SQL語句,交由ADO.NET物件執行到資料庫,如果資料存在返回則會回寫到CLR物件中。

以上概括了所有ORM的實現過程,這裡比較典型的是Dapper,它之所以小巧而高效能的原因正是因為該框架中沒有綠色的部分,可以說是所有ORM框架中最為精簡的(可能還會存在其他類似的框架,這裡僅用Dapper作為典型案例說明),因此沒有多餘的部分自然小而快。然而綠色部分的存在就決定了該ORM框架的功能到底有多強,剛好目前世面上的ORM框架中只有EF是完全實現了綠色框中的各個元素,並且支援最為全面,所以EF必然重而慢(至少EF6相對其他框架是比較差的)。

典型ORM框架實現

  1.  Dapper:需要自己手寫SQL語句完成操作,比較簡單直接不過還有DapperExtensions的幫助,可以在插入、修改及刪除時不用寫SQL語句全物件操作,這裡需要指出的是它應該不能算是一個完全的ORM框架,因為日常開發佔比多的查詢還是需要手寫SQL,無法物件型別化。

  2. Nhibernate:這是個比較久遠的框架,該框架需要自己配置元資料功能上會比DapperExtensions強一點,不過對於查詢表示式沒有太好的支援。

  3. SqlSugar,Chloe.ORM:此類框架雖然不能支援LINQ但是可以實現自己一套Lambda表示式用於生成SQL語句,總之其總體思想就是把SQL語法轉換成表示式語法,比較典型的是會用表示式中的變數名用作生成SQL語句中的別名,而且附加功能會比較多,總之比較靈活實用。

  4. Linq2db:此類ORM算是實現的比較完整的ORM流程(見上圖),而且支援眾多的資料庫,總之功能算是比較強大了。

EF功能最強的ORM

 EF是目前為止功能最強的ORM,這個相信大家沒有什麼爭議,大家可以參考一下這份文件 (EF Core and EF6 Feature by Feature Comparison),其中列出了EF6與EFCore的功能特性,相信沒有哪個ORM框架實現了其中大部分的特性,下面隨便列舉幾個特性:

  • 全面支援LINQ查詢,有不少框架也會支援LINQ可是使用時會發現有些寫法是不支援的,但是EF是目前支援最全的,在截止到目前為止EFCore的正式版本中也沒有完全實現EF6對LINQ的支援。

  • EFCore效能提升:從上一遍部落格(Mego(1))中可以看出對於個功能強大的ORM框架而言EFCore已提升相當多了總之已非常接近原生的ADO.NET框架了。

  • 物件繼承:這是個很好的設計,它可以讓你將關係資料庫實現物件繼承化,在資料表中也會存在等價父子表。

  • 資料庫遷移功能,無論你怎麼看待這個功能,但是對於開發而言這個功能太實用了,相信大家在很多DEMO中可以見到使用EF生成資料庫。

  • 物件的集合屬性展開,通常我們在取訂單時也希望一起把明細也取出來,更多的情況是還可以把訂單資料分頁,這個功能在大部ORM都是沒有的需要自己處理,然後在EF中只需要寫一行程式碼就能搞定。

  • 多對多關係:這個也是個比較常用但是也在其他ORM比較少見的功能,EF也是目前支援最好的。

  • EF框架對接了微軟的眾多其他需要資料訪問的框架,例如ASP.NET Identity,ASP.NET WebApi ODATA。

EF與EFCore缺陷

 雖然EF或EFCore功能已經很強大了,不過在開發過程中還有很多不足的地方,也有很多缺陷這也是導致有很多人使用之後而放棄的原因之一(當然還有EF6的效能問題)。下面將列一下EF6的不足給大家參考。

資料更新功能不足

在EF中資料提交的功能一直不行,在EF6中所有的資料操作都是單個物件分別提交了,這也就表示在大量資料提交過程中每個物件都要傳送一次資料庫,這也是導致為什麼效能底下了,而且更新及刪除資料一定要從資料庫取回物件或附加物件才能操作。這個問題在EFCore中都得到解決而且效能比較好如前文的測試可見。但是EF到目前都不能支援條件更新、條件刪除。不過這個問題可以使用這個框架(Entity Framework Plus)解決,不過這個框架是收費的。

建立時間修改時間問題

 我們在業務系統開發時常會遇到建立和更新資料時需要記錄建立時間或修改時間的情況,不過目前無論是EF或EFCore中都只能用觸發器來解決,不過當資料表變多時維護這些觸發器也是個比較麻煩的事情。有人會想到用預設值,這個EF中是支援的,但是EF不能選擇性的插入或更新指定的欄位,這是個兩難的情況。

建立及修改時間只是這類問題的一個例子,這類問題可以概括為我們在插入或更新資料時希望指定欄位是呼叫相應的資料表示式生成的,而不是應用程式的值,這裡可以舉個例項給大家看,在一個負載均衡的架構中我們需要在建立訂單時生成訂單流水號,這個流水號一定要唯一且連續,在這種多伺服器併發的情況下只有在資料庫生成這個流水號才是比較好的選擇,這樣可以充分保證當提交失敗時新分的流水號可以歸還,同時也保證大家依次生成不會重複且連續,這個時候就很需要ORM可以自動使用單號生成函式來生成值。

EF中的曲線解決,在EF6中插入、更改或刪除操作是支援儲存過程對映的,大家可以參考這個文章(Entity Framework Code First Insert, Update, and Delete Stored Procedures (EF6 onwards))裡面有詳細的說明,這裡我們可以做文章,我們修改儲存過程改為呼叫我們指定的表示式即可,這樣可以不用觸發器,也能達到目的。這裡有人會問維護儲存也需要比較大代價,這裡我們可以重寫EF的資料庫遷移生成儲存過程的程式碼來統一處理。

實體集合屬性多層展開

 無論是EF或EFCore中有一個Include函式,用來在返回實體物件時顯示包含它的集合或物件屬性,這個操作被稱為屬性展開,這裡先這麼稱呼。不過可以展開物件屬性(例如訂單 -> 客戶 -> 負責人 -> 公司),也可以展開集合屬性(例如 訂單 -> 明細集合),這時集合下面的屬性就不能展開了例如我希望得到(例如 訂單 -> 明細集合 -> 產品)這是不支援的。

補充EF實際內部程式碼是可以支援這種操作但是沒公開,如果有對ASP.NET WebApi OData有所瞭解的朋友可以知道,如果你的OData服務是建立在EF基礎之上的,在ODATA語法中可以支援多級資料展開的,並且在EF6中得到很好的實現,不過這些的前提是你的資料庫是SQL Server。

物件關係限制

例如一個訂單會有多個明細,每個明細都會對應一個產品,從邏輯上我們就可以認為訂單和產品是多對多關係了,但是在EF中就沒有這麼自由的可以操作了。

在EF中關係的操作有很多限制,這個特別在多對多關係中很嚴重,EF6中會生成一個隱藏的關係實體用來建立關聯,這些都是你不能控制的,而且在關係連結中要求主表一定要是主鍵,不過在EFCore中這些問題得到一些解決,不過EFCore目前不能支援多對多關係,至少目前的Flush API中還沒有看到。

時間戳

感覺EF中不少特性都是為SQL Server設計的,EF中強制時間戳必須CLR屬性是位元組陣列,不過在MySQL中的時間戳其實就是日期時間。

多資料庫實現差異

EF和EFCore是支援多個數據庫,其原理是EF定義的前文所說的中間表示式,然後再交給各方自行實現後續的操作。這種設計有如下缺點:

  • 資料庫支援綁定了資料訪問元件,例如MySQL.Data.Entity這個元件必須需要在MySQL.Data元件下工作,這時如果你想換個訪問MySQL的元件是不可以的。

  • 資料庫提供程式實現代價太大,如果大家有時間可以去看下EntityFramework.SqlServer或MySQL.Data.Entity裡的實現程式碼,這些都是著名公司微軟和Oracle維護的程式碼,其中的實現程式碼非常複雜,對於一般的團隊而言可以算是一個非常大的專案。

  • 資料庫提供程式實現質量,還是以MySQL.Data.Entity為例子,每個提供程式並不像微軟那樣完全實現EF在SQL Server中的各個功能,而且程式碼非常複雜問題會很多,即使是Oracle所維護的程式碼也是同樣糟糕,下面分享MySQL.Data.Entity的兩個例子。

MySQL.Data.Entity問題一:這個元件在MySQL5.6由於資料庫的主鍵限制在700多個位元組,因此它在自動遷移資料庫操作時會報錯主鍵過長錯誤,等於這個是完全不能用的。

MySQL.Data.Entity問題二:這個元件在MySQL5.7中,由於資料庫的BUG(虛擬列如"SELECT 1"的IS NULL判斷錯誤問題)會導致特定的LINQ表示式生成的SQL指令碼是不能得到正確結果的。

MySQL.Data.Entity問題三:對於繼承的SQL生成這一部分是完全沒有實現的,你會發現所生成的指令碼都是錯的。

MySQL.Data.Entity問題四:EF的Include操作的具體實現是依賴於CROSS APPLY(SQL Server)語法的,不過MySQL中完全沒有,也很寫出替代語句,因此這個功能在MySQL下等於是不存在的。

以上是因為我參加了EF對接MySQL的改造專案,這是我和我的團隊折騰了幾個月才得出的一些總結,不過好在我們最後通過修改MySQL.Data.Entity原始碼解決了這些問題。

總之其他框架不能確定,但是在EF中各個資料庫的支援程度是很不對稱的。

總結

在EntityFramework長達10年的發展歷程中,開始發展很快,但是後面到EntityFramework6.1.3(2015年3月)這個版本時就好像是EntityFramework的結束,之後EntityFramework6就再沒有此實制上的更新了直到今天,應該是微軟已經放棄它轉向EntityFrameworkCore上,不過EntityFrameworkCore的發展也沒有這麼快,至今還沒有超過EntityFramework6,所以直到今天微軟都不敢對外宣佈放棄EntityFramework6。

以上是我的個人見解,僅供參考。

原文地址 http://www.cnblogs.com/CarefreeXT/p/8729085.html

.NET社群新聞,深度好文,歡迎訪問公眾號文章彙總 http://www.csharpkit.com

640?wx_fmt=jpeg