本節內容,涉及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倍