IQueryable和IEnumerable 快讀《ASP.NET Core技術內幕與項目實戰》EFCore2.5:集合查詢原理揭秘

本節內容,涉及4.6(P116-P130) 。主要NuGet包:如前述章節
一、LINQ和EFCore的集合查詢擴展方法的區別
1、LINQ和EFCore中的集合查詢擴展方法 , 雖然命名和使用完全一樣,都兩者定義在不同的命名空間下,是不同的方法 。PS:LINQ定義在System.Linq中,EFCore定義在Microsoft.EntityFrameworkCore中
【IQueryable和IEnumerable 快讀《ASP.NET Core技術內幕與項目實戰》EFCore2.5:集合查詢原理揭秘】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返回IEnumerablevar nums = new int[] { 1, 2, 3, 4 };var numsNew = nums.Where(n => n > 2);//EFCore返回IQueryableusing 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,每頁顯示幾條-pageSizevoid OutputPage(int pageIndex, int pageSize){using var ctx = new MyDbContext();//獲取IQueryable集合booksvar books = ctx.Books();//復用books,計算集合總條數 。LongCount方法和Count的功能一樣long count = books.LongCount();//按每頁顯示條數pageSize,計算總頁數//使用了Math的Ceiling方法,如有小數 , 取天花板值,最后轉換類型為longlong 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條

推薦閱讀