快讀《ASP.NET Core技術內幕與專案實戰》EFCore2.5:集合查詢原理揭祕(IQueryable和IEnumerable)

2022-10-29 15:02:18

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