Ado.net Entity FrameWork的性能問題
當我們需要大批量追加數據,或者一次查詢海量數據的時候,必須注意,Ado.net Entity Framework的效率幾乎是無法忍受的。簡單的例子,我們將中國股市歷年的日線,約500萬條數據插入數據庫,使用Ado.net一般僅需要2分鐘左右,而使用EF,則這個時間會變為半小時的樣子。當我們要一次性的將所有數據讀取到內存的時候,這個效率同樣的令人難以忍受。所以,要注意以下要點:
1、使用Ado.net Entity,使用Esql性能略好,因為Linq To entities將首先轉換為esql再到sql語句。不過,我們多數的時候仍將使用Linq查詢,這樣相對來說知識能夠復用,而無需急于學習Esql。
2、如果是查詢一條記錄,用GetObjectByKey
一般情況下,EF每次查詢都會從數據庫中獲取資料,不會使用緩存,這樣效率較低。此時可以考慮使用GetObjectByKey,這樣,若該記錄已經在緩存中則不會再次向數據庫發出Sql命令,不過如果該記錄
在緩存中不存在,則會引發異常,所以我們通常使用TryGetObjectByKey
using (QuoteEntities entity = new QuoteEntities())
{
object quote;
EntityKey key = new EntityKey("Quote", "StockCode", "300001");
entity.TryGetObjectByKey(key, out entity);
}
3、如果無需對查詢的結果進行更新,則使用Notracking查詢,默認情況下系統預設的是MergeOption.AppendOnly
using (QuoteEntities entity = new QuoteEntities())
{
Quote quote= entity.Quotes.Execute(MergeOption.NoTracking).Where(a => a.Stockcode == "300001").FirstOrDefault();
}
4、如果某個查詢會多次使用,用預編譯查詢:我們每次發出Linq To entities查詢的時候,系統會將其編譯為Esql,然后使用Provider轉換為相應數據庫的Sql語句,這個過程很長。
我們可以編譯好查詢,這樣EF會緩存編譯的結果,這樣,后續的查詢就無需執行從linq到Esql再到Sql的轉換,速度會提高。
private static readonly Func<QuoteEntities, string ,User> CompiledQueryGetByStockCode =
CompiledQuery.Compile<QuoteEntities, string, User>((entity, stockCode) => entity.Quotes.Where(a=>a.StockCode == stockCode).FirstOrDefault());
然后,執行該查詢:
using (QuoteEntities entity = new QuoteEntities())
{
Quote quote= CompiledQueryGetByStockCode.Invoke(entity, "300001");
}
5、可以使用原生的Ado.net命令,但EF的Context中的Connection并不能直接利用,因為其預設為執行ESQL,那么要直接執行Sql,包括如下三個命令:
ObjectContext.ExecuteStoreCommand(),用來執行非Select的Sql命令。
ObjectContext.ExecuteStoreQuery<T>(),用來執行Select的Sql命令,返回實體集合。
ObjectContext.Translate<T>:這個是用來銜接Ado.net的,當你得到一個DbDataReader的時候,可以將結果轉換成實體集合。
但實際上只有查詢比較有效。
6、批量insert的時候,只能使用原生Ado.net,自己根據Factory創建DbConnection對象和insert命令。當然,可以為DbCommand建立兩個擴展方法,添加參數和為參數賦值。當然,批量insert應該包裹在
事務中才能保證速度,否則一些數據庫默認是每insert一條就開啟一個事務,性能低下。我們可以每insert數萬條記錄才提交一次事務,速度會更快些。
7、在以性能為主的應用中,不建議使用EF的實體類,當然伴隨而來的,是不建議使用EF:
在使用Sqlite追加數據的時候,在資源管理器中觀察文件變化,文件大小每次緩緩的增加4K的樣子,但是在不使用EF的實體對象而單純使用Ado.net的情形下,每次增加1M的樣子,要快得多。由此可見即使將數據讀入到EF的實體類,然后使用Ado.net直接insert到數據庫,性能的下降也在10倍以上,這或許和tracking有關。
而使用原生Ado.net,常見的DbHelper方式和反射到實體類的方式,都有些奇怪。
事實上,建立輕量級的DbHelper,只要為DbCommand命令建立兩個擴展方法處理參數問題,實體類實現一個IEntity接口(用于使用序號方式為各屬性Get或Set),再實現兩個擴展方法,即將DataReader返回的對象,轉換成實體類的IEnumerator,將實體對象轉換為DbParameter以作為insert、update的輸入參數,則數據訪問可簡單的實現。當然,為了避免寫太多Sql語句,為每個實體對象中加入insert、GetByKey之類的支持也是可行的。
從這個角度來說,我們將實體對象和實體集合對象,混合在實體類中當然也是可行的,不過我們可以建立專門的對應類,繼承自定義的泛型Repository<實體>來處理,項目結構更為簡單,與數據庫有關的全部放在Data目錄下面。當然,由于實體對象需要創建才能執行其方法,因此涉及到實體對象集合的方法往往寫成靜態方法更合適。
不過,考慮到軟件系統,什么都無所謂,惟快不破,則EF實際上已經淡出視野,并無實際價值。

浙公網安備 33010602011771號