十年河東,十年河西,莫欺少年窮。html
EF就如同那個少年,ADO.NET則是一位壯年。畢竟ADO.NET出生在EF以前,而EF所走的路屬於應用ADO.NET。sql
也就是說:你所寫的LINQ查詢,最後仍是要轉化爲ADO.NET的SQL語句,轉化過程當中無形下降了EF的執行效率。數據庫
可是,使用EF的一個好處就是系統便於維護,減小了系統開發時間,下降了生成成本。數組
OK,上述只是作個簡單的對比,那麼在實際編碼過程當中,咱們應當怎樣提高EF的性能呢?服務器
工欲善其事,必先利其器。併發
咱們使用EF和在很大程度提升了開發速度,不過隨之帶來的是不少性能低下的寫法和生成不過高效的sql。app
雖然咱們可使用SQL Server Profiler來監控執行的sql,不過我的以爲實屬麻煩,每次須要打開、過濾、清除、關閉。ide
在這裏強烈推薦一個插件MiniProfiler。實時監控頁面請求對應執行的sql語句、執行時間。簡單、方便、針對性強。工具
如圖:post
關於MiniProfiler的使用,你們可參考:MiniProfiler工具介紹(監控加載用時,EF生成的SQL語句)--EF,迷你監控器,哈哈哈
一、EF使用SqlQuery
上述已經說的很明白了,EF效率低於ADO.NET是由於LINQ-TO-SQL的過程消耗了時間。而使用SqlQuery則能夠直接寫SQL語句。
固然,若是你想獲得更快的執行速度,你也能夠在數據庫上寫存儲過程PROC
關於SqlQuery的用法,在此不做解釋。
二、EF使用AsNoTracking(),無跟蹤查詢技術(查詢出來的數據不能夠修改,若是你作了修改,你會發現修改並不成功)
2.一、測試修改:
var student = context.Student.AsNoTracking().Where(A => A.Id == 2).FirstOrDefault() ; student.StuName = "毛毛"; context.SaveChanges();
上述代碼嘗試修改數據,程序運行完之後,咱們會發現數據庫Id爲2的學生的姓名並無修改,所以,採用無跟蹤查詢技術獲得的數據是不能夠進行修改的。
2.二、性能測試:
代碼測試以下:
public ActionResult Index() { var profiler = MiniProfiler.Current; using (profiler.Step("高性能查詢Student的數據")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var a = context.Student.AsNoTracking().Where(A => A.StuName.Contains("張")).ToList(); } } using (profiler.Step("查詢Student的數據")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b = context.Student.Where(A => A.StuName.Contains("張")).ToList(); } } return View(); }
性能對好比下:
注意:(由於我使用的是本地數據庫,因此效率差異不是很大,若是是遠程數據庫且數據量比較大,性能會提高不少,有測試證實:其性能可提高4~5倍)
三、性能提高之AsNonUnicode
代碼測試以下:
public ActionResult Index() { var profiler = MiniProfiler.Current; using (profiler.Step("查詢Student的數據")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b = context.Student.Where(A => A.StuName=="趙剛").ToList(); } } using (profiler.Step("高性能查詢Student的數據")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var a = context.Student.AsNoTracking().Where(A => A.StuName == DbFunctions.AsNonUnicode("趙剛")).ToList(); } } return View(); }
性能對好比下:
從上圖能夠看出,生成了兩條基本相同的SQL語句,惟獨不相同的地方是:不加AsNonUnicode SQL中會有 N,加了AsNonUnicode後,SQL中沒有N
使用 N 前綴(查詢過程當中須要把數據庫默認格式轉化爲Unicode 格式來查詢,所以:性能被拉低)
在服務器上執行的代碼中(例如在存儲過程和觸發器中)顯示的 Unicode 字符串常量必須以大寫字母 N 爲前綴。即便所引用的列已定義爲 Unicode 類型,也應如此。
不使用 N 前綴
若是不使用 N 前綴,字符串將轉換爲數據庫的默認代碼格式。這可能致使不識別某些字符。
所以,關於 AsNonUnicode 的的使用,還要結合具體狀況。
四、多字段組合排序(字符串)先按照學號排序,再按姓名排序(請將排序OrderBy放在構造LINQ的最後)
錯誤代碼以下:
using (profiler.Step("查詢Student的數據")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b1 = context.Student.Where(A => A.StuName.StartsWith("王")).OrderBy(A => A.StuNum).OrderBy(A => A.StuName).ToList(); } }
正確代碼以下:
using (profiler.Step("高性能查詢Student的數據")) { using (BingFaTestEntities context = new BingFaTestEntities()) { var b2 = context.Student.Where(A => A.StuName.StartsWith("王")).OrderBy(A => A.StuNum).ThenBy(A => A.StuName).ToList(); } }
由上圖獲得的結果分析可知:錯誤代碼連續使用兩個OrderBy,致使後面的OrderBy覆蓋了前面的OrderBy,也就是說:錯誤代碼是按照姓名排列的。
所以,涉及連續排序時,要用ThenBy。
五、foreach循環的陷進
5.一、關於延遲加載
請看上圖紅框。爲何StudentId有值,而Studet爲null?由於使用code first,須要設置導航屬性爲virtual,纔會加載延遲加載數據。
加了virtual後,咱們就可使用延遲加載了。可是,若是用上述的ForEach循環,會產生嚴重的性能問題。
以下:
咱們經過 MiniProfiler工具 監控下生成的SQL語句,以下
生成了101條SQL語句,是否是很嚇人。
那咱們應當怎麼正確的使用懶加載呢?
解決方案:使用Include顯示鏈接查詢(注意:須要手動導入using System.Data.Entity 否則Include只能傳表名字符串)。
加上了Include後,懶加載就變成了顯示加載,也就是說帶有Virtual的懶加載字段信息會被一次加載出來,所以:使用 Include 後,只會生成一條SQL語句!
再看MiniProfiler的監控(瞬間101條sql變成了1條,這其中的性能可想而知。)
所以,性能會大大滴提高哦。
六、AutoMapper的使用
所謂AutoMapper即:自動映射,關於AutoMapper的使用,你們可參考個人博客:AutoMapper自動映射
下面結合數據庫來看以下示例:
數據表關係:
create table Dept ( Id int identity(1,1) not null, deptNum varchar(20) not null primary key, deptName nvarchar(20) default('計算機科學與工程系'), ) create table Student ( Id int identity(1,1) not null, StuNum varchar(20) primary key, deptNum varchar(20) FOREIGN KEY (deptNum) REFERENCES Dept (deptNum), StuName nvarchar(10),-- StuSex nvarchar(2) default('男'), AddTime datetime default(getdate()), )
很簡單。系表和學生表,有個外鍵deptNum,
EF中生成的DTO以下:
namespace BingFa.Entity { using System; using System.Collections.Generic; public partial class Student { public int Id { get; set; } public string StuNum { get; set; } public string deptNum { get; set; } public string StuName { get; set; } public string StuSex { get; set; } public Nullable<System.DateTime> AddTime { get; set; } public virtual Dept Dept { get; set; } } } namespace BingFa.Entity { using System; using System.Collections.Generic; public partial class Dept { public Dept() { this.Student = new HashSet<Student>(); } public int Id { get; set; } public string deptNum { get; set; } public string deptName { get; set; } public virtual ICollection<Student> Student { get; set; } } }
Model層
public class StudentModel { public int Id { get; set; } public string StuNum { get; set; } public string deptNum { get; set; } public string StuName { get; set; } public string StuSex { get; set; } public Nullable<System.DateTime> AddTime { get; set; } public string deptName { get; set; } }
測試代碼以下:
由上述代碼得知,咱們須要根據導航屬性獲取系名。
同理,若是你有不少導航屬性,你亦能夠多寫幾回 ForMember(......) ,可是這樣作會陷入延遲加載的陷阱。
針對上述的寫法,咱們的監測以下:
能夠看出居然生成了兩條SQL語句,若是你用了N個導航屬性,那麼就會生成N+1個SQL語句,這顯然是不能接受的,怎麼辦呢?
同上述,ForEach的陷阱同樣,咱們能夠派上Include,以下:
加上了AsNoTracking無跟蹤查詢技術,這個是用來提高查詢性能。同時加上了Include,用於顯示加載,從而避免了懶加載生成SQL的問題。
監測以下:
由此可知,僅僅生成了一條SQL語句,SQL查詢性能也提高了不少,所以在使用AutoMapper時,切記別陷入這種陷阱。
其實,說白了,其實都是懶加載惹的禍,用很差的話,懶加載會讓你很累的哦。
七、count(*)被你用壞了嗎(Any的用法)
要求:查詢是否存在名字爲「張三2」的學生。(你的代碼會怎樣寫呢?)
用第一種?第二種?第三種?呵呵,我之前就是使用的第一種,而後有人說「你count被你用壞了」,後來我想了想了怎麼就被我用壞了呢?直到對比了這三個語句的性能後我知道了。
看到監控後,瞬間驚呆了,count(*)的性能居然最低,Any的性能最高。性能之差竟有三百多倍,count確實被我用壞了。(我想,不止被我一我的用壞了吧。)
咱們看到上面的Any幹嗎的?官方解釋是:
我反覆閱讀這個中文解釋,一直沒法理解。甚至早有人也提出過一樣的疑問《實在看不懂MSDN關於 Any 的解釋》
因此我我的理解也是「肯定集合中是否有元素知足某一條件」。咱們來看看any其餘用法:
要求:查詢教過「張三」或「李四」的老師
實現代碼:
兩種方式,之前我會習慣寫第一種。固然咱們看看生成過的sql和執行效率以後,見解改變了。
效率之差竟有近六倍。
咱們再對比下count:
得出奇怪的結論:
八、動態建立LINQ子查詢
查詢姓 張 李 王 的男人
LINQ 以下:
var Query = from P in persons1 where (P.Name.Contains("張") || P.Name.Contains("李") || P.Name.Contains("王"))&&P.Sex=="男" select new PersonModel { Name = P.Name, Sex = P.Sex, Age = P.Age, Money = P.Money };
如今需求變動以下:查詢姓 張 李 王 的男人 而且 年齡要大於20歲
LINQ 變動以下:
var Query = from P in persons1 where (P.Name.Contains("張") || P.Name.Contains("李") || P.Name.Contains("王"))&&P.Sex=="男"&&P.Age>20 select new PersonModel { Name = P.Name, Sex = P.Sex, Age = P.Age, Money = P.Money };
好了,若是您認爲上述構建WHERE子句的方式就是動態構建的話,那麼本篇博客就沒有什麼意義了!
那麼什麼樣的方式纔是真正的動態構建呢?
OK,我們進入正題:
在此我提出一個簡單需求以下:
我相信個人需求提出後,你用上述方式就寫不出來了,個人需求以下:
請根據數組中包含的姓氏進行查詢:
數組以下:
string[] xingList = new string[] { "趙", "錢", "孫", "李", "周", "吳", "鄭", "王", "馮", "陳" };
在這裏,有人可能會立馬想到:分割數組,而後用十個 || 進行查詢就好了!
我要強調的是:若是數組是動態的呢?長度不定,包含的姓氏不肯定呢?
呵呵,想必寫不出來了吧!
還好,LINQ也有本身的一套代碼能夠實現(若是LINQ實現不了,那麼早就沒人用LINQ了):
因爲代碼比較多,在此你們可參考:LINQ 如何動態建立 Where 子查詢
代碼以下:
須要指出的是:
Expression.Or(con, condition); 邏輯或運算
Expression.And(con, condition); 邏輯與運算
代碼分析:
生成的LINQ子查詢相似於:c=>c.Tags.Contains(s) || c=>c.Alias.Contains(Alias)....
九、真分頁與假分頁(瞭解 IQueryable,IEnumerable的區別)
你們都知道分頁是很是經常使用的功能,可是在使用EF寫分頁語句的時候,稍有不慎,真分頁便會成爲假分頁:
上述兩個看似相似的LINQ語句,實際執行起來效率差了不少。其緣由是ToList使用的位置,當你ToList()時,EF會將linq轉化爲SQL,而後執行。
第一個LINQ咱們可理解爲:先把數據所有都查詢出來,而後分頁
第二個LINQ咱們可理解爲:只查詢分頁所需的N條數據。若是你有100萬條數據,第一種方法會所有查詢出來,第二種方法僅僅會查詢分頁所需的10條數據,其性能對比可想而知。
十、批量刪除和修改
不知道你是否研究過EF的插入刪除和修改操做,當你批量操做數據的時候,經過SQL Server Profiler能夠明顯看到產生了大量的Insert,Update語句,效率很是低;由於他插入一條數據,會對應生成一條Insert語句,當你的list中有10萬條數據時,就會生成10萬條插入語句!不過還好我們有對策:Entity Framework Extendeds ,EF擴展類完美解決批量操做問題:
要使用AddRange,一次性插入10萬條數據。
十一、EF使用存儲過程
在此貼出個人存儲過程(我這個存儲過程也是處理併發的存儲過程),關於併發處理你們可參考:C# 數據庫併發的解決方案(通用版、EF版)
create proc LockProc --樂觀鎖控制併發 ( @ProductId int, @IsSuccess bit=0 output ) as declare @count as int declare @flag as TimeStamp declare @rowcount As int begin tran select @count=ProductCount,@flag=VersionNum from Inventory where ProductId=@ProductId update Inventory set ProductCount=@count-1 where VersionNum=@flag and ProductId=@ProductId insert into InventoryLog values('插入一條數據,用於計算是否發生併發',GETDATE()) set @rowcount=@@ROWCOUNT if @rowcount>0 set @IsSuccess=1 else set @IsSuccess=0 commit tran
EF執行存儲過程的方法以下:
十二、EF Contains、StartsWith、EndsWith
請看以下代碼:
生成了按照Unicode字符集進行的模糊查詢,生成的SQL帶N
如何優化呢?首先咱們按照本篇博客第三條:三、性能提高之AsNonUnicode 咱們按照數據庫默認編碼查詢來提高效率。
根據生成的SQL語句,能夠看出查詢沒有帶N,執行時間爲32.4秒,效率增長一倍。
除了上述優化以外,還要看公司項目的具體要求,若是要求進行雙向匹配,那麼你只能老老實實的採用Contains,若是公司只要求單項匹配,你能夠採用StartsWith、EndsWith
固然,要想模糊查詢相率高些,單項匹配固然最好,具體還要看項目需求哦
1三、EF預熱
使用過EF的都知道針對全部表的第一次查詢都很慢,而同一個查詢查詢過一次後就會變得很快了。
假設場景:當咱們的查詢編譯發佈部署到服務器上時,第一個訪問網站的的人會感受到頁面加載的十分緩慢,這就帶來了很很差的用戶體驗。
解決方案:在網站初始化時將數據表遍歷一遍
在Global文件的Application_Start方法中添加以下代碼(代碼以下(Entity Framework的版本至少是6.0才支持)):
using (var dbcontext = new BingFaTestEntities()) { var objectContext = ((IObjectContextAdapter)dbcontext).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); }
咱們作個測試:
12.一、第一次運行程序,不進行EF預熱的:
12.二、一樣從新運行程序,進行EF預熱的:
執行速度:
由上圖能夠,在進行了EF預熱後,加載時間爲856.9毫秒,而不進行EF預熱加載用時1511.5毫秒,由此可知,加上預熱代碼後,第一次加載速度幾乎快了一倍。