在知乎回答了下,順手轉回來。
Enity Framework已經是.NET下最主要的ORM了。而ORM從一個Mapping的概念開始,到現在已經得到了一定的升華,特別是EF等對ORM框架面向對象能力的升華。切實地說,就是ORM讓數據庫在整個應用過程中更好地被封裝和抽象化。
ORM一開始只是Mapping,最基礎的就是表與類的對應、Column和屬性的對應,這只是最基礎的。在這個層次上,數據庫對象通過Mapping在面向對象語言層面,也就是業務層面被封裝成了業務對象,然后允許以操作業務對象的方式對數據庫進行操作。
但是,在很長時間里,ORM的提升都被對象與關系間的“阻抗失配”困擾。一直以來很多ORM的水平都只是維持在了用對象的方式進行CRUD而已,除了減少代碼錯誤、提高簡單查詢的開發效率,在復雜查詢、性能等等一些方面結果都還是要跨層回到底層的操作框架比如ADO.NET甚至存儲過程去解決問題。
所以,在應用場景上來說簡單查詢的場景EF和其他ORM都是能夠勝任的。
從應用場景說起,這點在B/S和C/S里也會很明顯。用戶使用Web的時候和使用桌面軟件的最大體驗不同是什么?——所見即所得。你在網頁上操作了半天,一個關閉就全沒了,還必須提交然后獲得下一個頁面才能把數據狀態和UI更新;而同樣在桌面上,你的操作比如畫圖,在操作的一瞬間結果就出來了。當然了,Web 2.0技術就在解決這個問題。
同樣在OO和RDBMS中的問題也在這里。
從OO的角度上看,你運行下一段代碼結果如何:
user.Name = "Indream Luo";
在OO里,就是user對象的Name屬性被更新了。如果是一個桌面軟件,那么用戶的名稱應該也更改了。
但是如果這個對象的數據是存在關系數據庫中,或者任意數據庫,那么結果都逃脫不出這個套路:
var object = db.Get(id); change(ref object); db.Update();
你需要把更新Push過去,將操作和數據持久化。
在存儲分層開始,推送更新就不可避免,哪怕在說面應用中,也是將對象的更新推送到了UI。ORM站立在應用場景不一致中間所要扮演的角色,就是一個潤滑劑的角色。
在我所能馬上想起的特性,就是EF和NHibernate的緩存機制。EF是一級緩存,NH是二級緩存,手動的話似乎EF也可以做到二級。然后在.NET下最重要的一點是有LINQ。LINQ在有合適的Provider的情形下可以把OO的序列化操作轉化成目標的序列化操作,在這里就是LINQ轉SQL,這樣就省去了拼接SQL、SQL注入等很多麻煩。另外LINQ延遲加載的特性也很大地減少了用戶控制SQL執行的工作。
在操作同步的基礎上,還有結構同步的問題。表結構和對象結構同步是使用ORM一大工作內容。EF有默認的生成工具,DB First、Model First、Code First三種模式提供選擇,加上自動生成同步SQL,選擇性是現在最廣的,NH也有一些相應的生成器,數據庫優先方面小弟LINQ to SQL的拖拽最驚艷。
在這個場景下,加上對相關工具的利用,EF等ORM適用於序列操作、減少數據庫操作管理和結構同步工作量,減少開發成本。
最后,不可回避的就是阻抗失配的問題。
對象關系模型和關系數據庫模型在以前很大程度上不一致,這是在以前。現在ORM要做的就是如何讓兩者更接近,讓一邊的特性能更順滑地體現在另一邊。
我 在早幾個月寫過一篇總結,關於最近一個項目EF使用的一些方法——《Entity Framework 與 面向對象》。太長就選重點來說明。
EF所做的涵蓋:類型匹配、對象結構、數據源區分。
類型匹配方面,就是把OO類型和數據庫類型進行匹配,這是ORM的基礎。基礎類型中的整數、浮點、字符串、日期這些不在話下,EF比較有特點的可能是枚舉類型(Enum)、復雜類型(Complex Type)、地理位置的功能,實現方式也比較理想。
對象結構方面是EF讓我最驚艷的地方。EF的Model,也就是Entity能實現集成關系,也可以通過此同步在表結構中;EF中通過對外鍵的控制,對引用和依賴關系實現得十分出色;支持虛類、對象層面的Get/Set、訪問控制都很好用。
插一段,通過使用Model First,我倒是發現數據庫的設計更加接近於范式了。因為LINQ和對象結構方面帶來的便利,我可以把表結構設計得更“合理”。比如如果要獲取user的上司的上司,假設每個User都只有一個上司,那么用EF或者一些ORM就是:
var bigBoss = user.Superior.Superior;
如果要寫SQL,感覺有點煩,算代碼量和可讀性已經能看出區別了。
最后是數據源區分。有一個問題是一個對象,它的數據不一定完全源於數據庫,或者一個數據庫,這個例子我常用。
比如User有三個字段:FirstName、LastName、FullName。可以知道FullName其實就是FirstName和LastName的拼接,如果創建Model/Entity,一般:
public partial class User { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { if (fullName == null) { this.fullName = String.Format("{0} {1}", this.FirstName, this.LastName); } return this.fullName; } } string fullName; }
而在數據庫里,只需要存儲FirstName和LastName,FullName作為計算值就可以了,而且還是延遲加載的。
更甚者,我們還可以從上面的例子延伸,在Entity中封裝一些數據庫操作:
public partial class User { public User Superior { get; set; } public User BigBoss { get { return this.Superior.Superior; } } }
此時在數據庫中只存儲一個Superior關系即可,BigBoss作為計算值就可以了。當然,你樂意還可以緩存和延遲加載,但EF已經處理了緩存了。
極端情況,確實是可以在表關系中“玩”不少面向對象的設計模式。
終上所述,EF適用於面向對象結構和特性優先性比較高的 場景。
那么相對地,也說說不適用的場景。
首先大家所詬病的是性能問題,這點希望不要拋開原理去說EF的性能。
EF由於其執行原理,性能損耗一般發生在:
- LINQ也就是Expression Tree創建和轉換成SQL的過程
- 緩存比對的過程
- 特殊操作實現不合理
- 極限性能壓力下的問題
- 性能泄露
1是不可避免的,2通過關閉比對或者緩存可以解決,3、4和5是主要問題。
特殊操作不合理舉例來說,比如遞歸,獲取一個樹結構的一條索引鏈。如果是通過OO來做,那么就是要么要往返很多次數據庫,要么要至少遍歷一次對象表,要么就是要加一些特殊的“丑陋的”索引字段。
極限性能壓力在包含上個問題的情況下擴展,比如SQL Server的存儲過程執行的特殊操作是最快的,純OO的方式肯定達不到。
這兩項特殊項通過更原生的數據庫方式去解決是最佳的解決方案。你可以和EF混用,也可以單獨使用,但不要妄想着有銀彈能同時解決所有問題。EF提供了SQL的執行方式。
性能泄露不是一個專有名詞,是我臨時用的。意思是因為EF導致的不必要的性能浪費。特別是LINQ的延遲加載特性,許多不清楚LINQ特性的開發人員容易將LINQ序列無謂地實例化,浪費了系統資源。通常會是:
- 遍歷查詢全表數據, 然后再在OO層面進行篩選
- 無謂地執行實例化,進行查詢,要么浪費緩存比對的資源,要么浪費查詢資源
我只能說這是開發人員水平問題,雖然出現問題后很難定位,特別是一般情況下都會造成內存泄露。
最后最常見的還是回到數據模型同步的問題。當數據模型更改后,需要同步,這時候如果已經有業務數據了,是一件麻煩的事情。EF的Migration我沒用過,是一個解決方案但似乎不那么完美。在一些非ORM應用的系統,SQL集中管理架構下,在這個場景,可能會更容易進行維護。