【轉】你必須知道的EF知識和經驗

【轉】你必須知道的EF知識和經驗html

注意:如下內容若是沒有特別申明,默認使用的EF6.0版本,code first模式前端

推薦MiniProfiler插件

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

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

雖然咱們可使用SQL Server Profiler來監控執行的sql,不過我的以爲實屬麻煩,每次須要打開、過濾、清除、關閉。sql

在這裏強烈推薦一個插件MiniProfiler。實時監控頁面請求對應執行的sql語句、執行時間。簡單、方便、針對性強。數據庫

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

數據準備

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

後面會給出demo代碼下載連接工具

foreach循環的陷進 

1.關於延遲加載性能

請看上圖紅框。爲何StudentId有值,而Studet爲null?由於使用code first,須要設置導航屬性爲virtual,纔會加載延遲加載數據。

2.關於在循環中訪問導航屬性的異常處理(接着上面,加上virtual後會報如下異常

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

解決方案:

  • 方案一、設定ConnectionString加上MultipleActiveResultSets=true,但只適用於SQL 2005之後的版本
  • 方案二、或者先讀出放置在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

本文以同步至《C#基礎知識鞏固系列

歡迎熱心園友補充!

相關文章
相關標籤/搜索