快讀《ASP.NET Core技術內幕與項目實戰》EFCore2.5:集合查詢原理揭秘(IQueryable和IEnumerable)
- 2022 年 10 月 29 日
- 筆記
- aspnetcore, AspNetCore/ABP/MASA/Harmony, efcore, IQueryable, SQL/NOSQL/EFCore/Dapper
本節內容,涉及4.6(P116-P130)。主要NuGet包:如前述章節
一、LINQ和EFCore的集合查詢擴展方法的區別
1、LINQ和EFCore中的集合查詢擴展方法,雖然命名和使用完全一樣,都兩者定義在不同的命名空間下,是不同的方法。PS:LINQ定義在System.Linq中,EFCore定義在Microsoft.EntityFrameworkCore中
2、我們將集合操作的擴展方法,劃分為兩類:①非立即執行方法,如Where、OrderBy、Select、GroupBy、Skip、Take、Include等;②立即執行方法:如Min、Max、Count、Sum、ToArray、ToList<T>、foreach等。
3、當執行非立即方法時,LINQ返回IEnumerable集合,EFCore返回IQueryable集合。兩者最大區別為:LINQ會立即在伺服器記憶體中執行計算(客戶端評估);而EFCore會延遲執行,只有當我們執行立即執行方法後,EFCore才會將之前定義的所有非立即執行方法,整合為SQL拋到資料庫執行(服務端評估)。
4、利用EFCore中IQueryable的特點,我們就可以充分利用客戶端評估和服務端評估,達到延遲執行、簡化程式碼、復用程式碼、平衡性能等目的
//LINQ返回IEnumerable var nums = new int[] { 1, 2, 3, 4 }; var numsNew = nums.Where(n => n > 2); //EFCore返回IQueryable using var ctx = new MyDbContext(); var books= ctx.Book.Where(a => a.Id > 0);
二、IQueryable的延遲執行案例
//利用IQueryable延遲執行,拼接複雜查詢 //定義一個複雜查詢的方法,接受參數①關鍵詞;②是否同時匹配書名和作者名;③是否按價格排序;④最高價格 void QueryBooks(string searchWords, bool searchAll, bool orderByPrice, double upperPrice) { using var ctx = new MyDbContext(); //查詢低於最高價 var books = ctx.Books.Where(b => b.Price <= upperPrice); //同時匹配書名和作者 if(searchAll) { books = books.Where(b => b.Title.Contains(searchWords) || b.AuthorName.Contains(searchWords)); } //只匹配書名 else { books = books.Where(b =>b.Title.Contains(searchWords)); } //按照價格排序 if(orderByPrice) { books = books.OrderBy(b => b.Price); } //立即執行方法,遍歷 foreach(var item in books) { Console.WriteLine($"書名:{item.Title},作者:{item.AuthorName}"); } } //調用方法 QueryBooks("LINQ", true, true, 30); //查詢書名或作者名,按價格排序 QueryBooks("LINQ", false, false, 50); //只查詢書名,不按價格排序
三、復用IQueryable的案例
//獲得一個IQueryable集合books,並三次復用它 var books = ctx.Books.Where(b => b.Price >=20); //使用books集合,執行一次立即查詢 Console.WriteLine(books.Count()); //再次使用books集合,執行第二次立即查詢 Console.WriteLine(books.Max(b => b.Price)); //第三次立即查詢 foreach (var item in books.Where(b => b.PubTime.Year > 2000)) { Console.WriteLine(item.Title); }
四、結合使用服務端評估和客戶端評估的案例
//使用立即執行方法ToList,執行SQL查詢(服務端評估),將結果存到伺服器的記憶體中 var books = await ctx.Books.Take(100000).ToListAsync(); //使用伺服器記憶體中的集合books,進行遍歷查詢,在伺服器上執行(客戶端評估) foreach (var item in books) { Console.WriteLine(item.Title); } //由於遍歷條數比較多,需要一定時間 //如果在遍歷過程中,我們關閉資料庫伺服器,程式仍然可以正常進行 //說明遍歷前,已經將數據下載到客戶端 //大多數情況下,我們應該復用IQueryable,但在方法返回IQueryable,或嵌套遍歷不同的DbSet時,需要考慮特別注意 //出錯情況1:方法返回IQueryable //方法中返回IQueryable時,會銷毀上下文 //正確應該返回:return ctx.Books.Where(b => b.Id>5).ToList(); IQueryable<Book> QueryBooks() { using var ctx = MyDbContext(); return ctx.Books.Where(b => b.Id>5); } foreach(var item in QueryBooks()) { Console.WriteLine(item.Title); } //出錯情況2:嵌套遍歷不同的DbSet //嵌套循環,導致兩個DataReader執行,大多數資料庫不允許多個DataReader同時執行 var books = ctx.Books.Where(b => b.Id > 1); foreach(var item1 in books) { Console.WriteLine(item1.Title); foreach(var item2 in ctx.Authors) { Console.WriteLive(item2.Id); } }
五、最後一個綜合案例:分頁查詢
//定義一個分頁查詢方法,參數為獲取第幾頁-pageIndex,每頁顯示幾條-pageSize void OutputPage(int pageIndex, int pageSize) { using var ctx = new MyDbContext(); //獲取IQueryable集合books var books = ctx.Books(); //復用books,計算集合總條數。LongCount方法和Count的功能一樣 long count = books.LongCount(); //按每頁顯示條數pageSize,計算總頁數 //使用了Math的Ceiling方法,如有小數,取天花板值,最後轉換類型為long long pageCount = (long)Math.Ceiling(count * 1.0 / pageSize); Console.WriteLine($"總頁數:{pageCount}"); //復用books,獲取指定頁碼的數據,並遍歷 //使用了Skip和Take方法 var pageIndexBooks = books.Skip((pageIndex - 1) * pageSize).Take(pageSize); foreach( var item in pageIndexBooks) { Console.WriteLine(item.Title) } } //調用方法 OutputPage(1,10); //第1頁,每頁顯示10條 OutputPage(3,5); //第3頁,每頁顯示5條
特別說明:
1、本系列內容主要基於楊中科老師的書籍《ASP.NET Core技術內幕與項目實戰》及配套的B站影片影片教程,同時會增加極少部分的小知識點
2、本系列教程主要目的是提煉知識點,追求快准狠,以求快速複習,如果說書籍學習的效率是影片的2倍,那麼「簡讀系列」應該做到再快3-5倍