1. 程式人生 > 實用技巧 >你必須知道的EF知識和經驗

你必須知道的EF知識和經驗

閱讀目錄

工欲善其事,必先利其器。我們使用EF和在很大程度提高了開發速度,不過隨之帶來的是很多效能低下的寫法和生成不太高效的sql。雖然我們可以使用SQL Server Profiler來監控執行的sql,不過個人覺得實屬麻煩,每次需要開啟、過濾、清除、關閉。在這裡強烈推薦一個外掛MiniProfiler。實時監控頁面請求對應執行的sql語句、執行時間。簡單、方便、針對性強。

注意:以下內容如果沒有特別申明,預設使用的EF6.0版本,codefirst模式

推薦MiniProfiler外掛

工欲善其事,必先利其器。

我們使用EF和在很大程度提高了開發速度,不過隨之帶來的是很多效能低下的寫法和生成不太高效的sql。

雖然我們可以使用SQL Server Profiler來監控執行的sql,不過個人覺得實屬麻煩,每次需要開啟、過濾、清除、關閉。

在這裡強烈推薦一個外掛MiniProfiler。實時監控頁面請求對應執行的sql語句、執行時間。簡單、方便、針對性強。

如圖:(具體使用和介紹請移步)

資料準備

新建實體:Score(成績分數表)、Student(學生表)、Teacher(老師表)

後面會給出demo程式碼下載連結

foreach迴圈的陷進

1.關於延遲載入

請看上圖紅框。為什麼StudentId有值,而Studet為null?因為使用code first,需要設定導航屬性為virtual,才會載入延遲載入資料。

2.關於在迴圈中訪問導航屬性的異常處理(接著上面,加上virtual後會報以下異常

"已有開啟的與此 Command 相關聯的 DataReader,必須首先將它關閉。"

解決方案:

  • 方案1、設定ConnectionString加上MultipleActiveResultSets=true,但只適用於SQL 2005以後的版本
  • 方案2、或者先讀出放置在List中

3.以上兩點僅為熱身,我們說的陷阱才剛剛開始!

然後我們點選開啟MiniProfiler工具(不要被嚇到

解決方案:使用Include顯示連線查詢(注意:需要手動匯入using System.Data.Entity 不然Include只能傳表名字串)。

再看MiniProfiler的監控(瞬間101條sql變成了1條,這其中的效能可想而知。

AutoMapper工具

上面我們通過Include顯示的執行表的連線查詢顯然是不錯的,但還不夠。如果我們只需要查詢資料的某些欄位呢,上面查詢所有欄位豈不是很浪費記憶體儲存空間和應用程式與資料庫資料傳輸頻寬。

我們可以:

對應監控到的sql:

我們看到生成的sql,查詢的欄位少了很多。只有我們顯示列出來欄位的和一個StudentId,StudentId用來連線查詢條件的。

是的,這樣的方式很不錯。可是有沒有什麼更好的方案或方式呢?答案是肯定的。(不然,也不會在這裡屁話了。)如果表字段非常多,我們需要使用的欄位也非常多,導航屬性也非常多的時候,這樣的手動對映就顯得不那麼好看了。那麼接下來我們開始介紹使用AutoMapper來完成對映:

注意:首先需要NuGet下載AutoMapper。(然後匯入名稱空間using AutoMapper;using AutoMapper.QueryableExtensions;)

我們看到上面查詢語句沒有一個個的手動對映,而對映都是獨立配置了。其中CreateMap應該是要寫到Global.asax檔案裡面的。其實也就是分離了對映部分,清晰了查詢語句。細心的同學可能注意到了,這種方式還免去了主動Include

我們看到了生成的sql和前面有些許不同,但只生成了一條sql,並且結果也是正確的。(其實就是多了一條CASE WHEN ([Extent2].[Id] IS NOT NULL) THEN 1 END AS [C1]。看起來這條語句並沒有什麼實際意義,然而這是AutoMapper生成的sql,同時我也表示不理解為什麼和EF生成的不同)

這樣做的好處?

  1. 避免在迴圈中訪問導航屬性多次執行sql語句。
  2. 避免了查詢語句中太多的手動對映,影響程式碼的閱讀。

關於AutoMapper的其他一些資料:

http://www.cnblogs.com/xishuai/p/3712361.html

http://www.cnblogs.com/xishuai/p/3700052.html

http://www.cnblogs.com/farb/p/AutoMapperContent.html

聯表查詢統計

要求:查詢前100個學生考試型別(“模擬考試”、“正式考試”)、考試次數、語文平均分、學生姓名,且考試次數大於等於3次。(按考試型別分類統計

程式碼如下:

看到這樣的程式碼,我第一反應是慘了。又在迴圈執行sql了。監控如下:

其實,我們只需要稍微改動就把101條sql變成1條,如下:

馬上變1條。

我們開啟檢視詳細的sql語句

發現這僅僅只是查詢結果集合而已,其中的按考試型別來統計是程式拿到所有資料後在計算的(而不是在資料庫內計算,然後直接返回結果),這樣同樣是浪費了資料庫查詢資料傳輸。

關於連線查詢分組統計我們可以使用SelectMany,如下:

監控sql如下:(是不是簡潔多了呢?

關於SelectMany資料:

http://www.cnblogs.com/lifepoem/archive/2011/11/18/2253579.html

http://www.cnblogs.com/heyuquan/p/Linq-to-Objects.html

效能提升之AsNonUnicode

監控到的sql

我們看到EF正常情況生成的sql會在前面帶上“N”,如果我們加上DbFunctions.AsNonUnicode生成的sql是沒有“N”的,當你發現帶上“N”的sql比沒有帶“N”的 sql查詢速度慢很多的時候那就知道該怎麼辦。

以前用oracle的時候帶不帶“N”查詢效率差別特別明顯,今天用sql server測試並沒有發現什麼差別。還有我發現EF6會根據資料庫中是nvarchar的時候才會生成帶“N”的sql,oracle資料庫沒測試,有興趣的同學可以測試下

效能提升之AsNoTracking

我們看生成的sql

sql是生成的一模一樣,但是執行時間卻是4.8倍。原因僅僅只是第一條EF語句多加了一個AsNoTracking。

注意:

  • AsNoTracking幹什麼的呢?無跟蹤查詢而已,也就是說查詢出來的物件不能直接做修改。所以,我們在做資料集合查詢顯示,而又不需要對集合修改並更新到資料庫的時候,一定不要忘記加上AsNoTracking。
  • 如果查詢過程做了select對映就不需要加AsNoTracking。如:db.Students.Where(t=>t.Name.Contains("張三")).select(t=>new (t.Name,t.Age)).ToList();

多欄位組合排序(字串)

要求:查詢名字裡面帶有“張三”的學生,先按名字排序,再按年齡排序。

咦,不對啊。按名字排序被年齡排序覆蓋了。我們應該用ThenBy來組合排序。

不錯不錯,正是我們想要的效果。如果你不想用ThenBy,且都是升序的話,我們也可以:

生成的sql是一樣的。與OrderBy、ThenBy對應的降序有OrderByDescending、ThenByDescending。

看似好像很完美了。其實不然,我們大多數情況排序是動態的。比如,我們會更加前端頁面不同的操作要求不同欄位的不同排序。那我們後臺應該怎麼做呢?

當然,這樣完成是沒問題的,只要你願意。可以這麼多可能的判斷有沒有感覺非常SB?是的,我們當然有更好的解決方案。要是OrderBy可以直接傳字串???

解決方案:

  1. guget下載System.Linq.Dynamic
  2. 匯入System.Linq.Dynamic名稱空間
  3. 編寫OrderBy的擴充套件方法

然後上面又長又臭的程式碼可以寫成:

我們看下生成的sql:

和我們想要的效果完全符合,是不是感覺美美噠!!

【注意】:傳入的排序欄位後面要加排序關鍵字 asc或desc

lamdba條件組合

要求:根據不同情況查詢,可能情況

  1. 查詢name=“張三” 的所有學生
  2. 查詢name=“張三” 或者 age=18的所有學生

實現程式碼:

是不是味到了同樣的臭味。下面我們來靈活組裝Lamdba條件。

解決方案:

這段程式碼我也是從網上偷的,具體連結找不到了。

然後我們的程式碼可以寫成:

有沒有美美噠一點。然後我們看看生成的sql是否正確:

EF的預熱

http://www.cnblogs.com/dudu/p/entity-framework-warm-up.html

count(*)被你用壞了嗎(Any的用法)

要求:查詢是否存在名字為“張三”的學生。(你的程式碼會怎樣寫呢?

第一種?第二種?第三種?呵呵,我以前就是使用的第一種,然後有人說“你count被你用壞了”,後來我想了想了怎麼就被我用壞了呢?直到對比了這三個語句的效能後我知道了。

效能之差竟有三百多倍,count確實被我用壞了。(我想,不止被我一個人用壞了吧。

我們看到上面的Any幹嘛的?官方解釋是:

我反覆閱讀這個中文解釋,一直無法理解。甚至早有人也提出過同樣的疑問《實在看不懂MSDN關於 Any 的解釋

所以我個人理解也是“確定集合中是否有元素滿足某一條件”。我們來看看any其他用法:

要求:查詢教過“張三”或“李四”的老師

實現程式碼:

兩種方式,以前我會習慣寫第一種。當然我們看看生成過的sql和執行效率之後,看法改變了。

效率之差竟有近六倍

我們再對比下count:

得出奇怪的結論:

  1. 在導航屬性裡面使用count和使用any效能區別不大,反而FirstOrDefault() != null的方式效能最差。
  2. 在直接屬性判斷裡面any和FirstOrDefault() != null效能區別不大,count效能要差的多。
  3. 所以,不管是直接屬性還是導航屬性我們都用any來判斷是否存在是最穩當的

透明識別符號

假如由於各種原因我們需要寫下面這樣邏輯的語句

我們可以寫成這樣更好

看生成的sql就知道了

第二種方式生成的sql要乾淨得多,效能也更好。

EntityFramework.Extended

這裡推薦下外掛EntityFramework.Extended,看了下,很不錯。

最大的亮點就是可以直接批量修改、刪除,不用像EF預設的需要先做查詢操作。

至於官方EF為什麼沒有提供這樣的支援就不知道了。不過使用EntityFramework.Extended需要注意以下幾點:

  1. 只支援sql server
  2. 批量修改、刪除時不能實現事務(也就是出了異常不能回滾)
  3. 沒有聯級刪除
  4. 不能同EF一起SaveChanges詳見

http://www.cnblogs.com/GuZhenYin/p/5482288.html

在此糾正個問題EntityFramework.Extended並不是說不能回滾,感謝@GuZhenYin園友的指正(原諒我之前沒有動手測試)。

注意:需要NuGet下載EntityFramework.Extended, 並匯入名稱空間:using EntityFramework.Extensions;

測試程式碼如下:(如果註釋掉手拋異常程式碼是可以直接更新到資料庫的)

using (var ctxTransaction = db.Database.BeginTransaction())
{
    try
    {
        db.Teachers.Where(t => true).Update(t => new Teacher { Age = "1" });

        throw new Exception("手動丟擲異常");

        ctxTransaction.Commit();//提交事務
    }
    catch (Exception)
    {
        ctxTransaction.Rollback();//回滾事務
    }
}

自定義IQueryable擴充套件方法

最後整理下自定義的IQueryable的擴充套件。

補充1:

First和Single的區別:前者是TOP(1)後者是TOP(2),後者如果查詢到了2條資料則丟擲異常。所以在必要的時候使用Single也不會比First慢多少。

補充2:

已打包nuget提供直接安裝Install-Package Talk.Linq.Extensions或nuget搜尋Talk.Linq.Extensions

https://github.com/zhaopeiym/Talk/wiki/Talk.Linq.Extensions_demo

結束:

原始碼下載:http://pan.baidu.com/s/1o8MYozw

*****************轉摘:https://www.cnblogs.com/zhaopei/p/5721789.html