IQueryable 和 IEnumerable 的區(qū)別
這是 EF Core 系列的最后一篇文章,按照上一篇的計(jì)劃,我們最后就講一講 IQueryable 和 IEnumerable 的區(qū)別。
點(diǎn)擊上方或后方藍(lán)字,閱讀 EF Core 系列合集。

在前面的一些例子中,我們會(huì)在使用 LINQ 查詢(xún)方法之后,又使用 ToList 等方法,將查詢(xún)結(jié)果轉(zhuǎn)換成集合。
如果我們不使用 ToList 呢?
比如這個(gè)示例:
using var context = new BloggingContext();
var posts = context.Posts.OrderBy(p => p.BlogId == 1).ToList();
var posts1 = context.Posts.OrderBy(p => p.BlogId == 1);
這里有兩個(gè)查詢(xún),一個(gè)使用了 ToList 方法,另一個(gè)沒(méi)有。
執(zhí)行這段代碼,在控制臺(tái)中查看日志:

可以看到只有一條 SQL 語(yǔ)句被執(zhí)行,按理說(shuō)我們有兩個(gè) LINQ 查詢(xún),應(yīng)該有兩條 SQL 語(yǔ)句,但是只有一條,這是為什么?
其實(shí),我們調(diào)用的 LINQ 查詢(xún)方法,本身是不會(huì)執(zhí)行查詢(xún)操作的。
簡(jiǎn)單來(lái)說(shuō),我們執(zhí)行的 LINQ 方法,只是轉(zhuǎn)換成了查詢(xún)表達(dá)式,被存儲(chǔ)在了 IQueryable 類(lèi)型的對(duì)象中。
只有當(dāng)我們需要數(shù)據(jù)的時(shí)候,比如當(dāng)我們使用 ToList 方法時(shí),EF Core 才會(huì)將整個(gè)出啊訊表達(dá)式,翻譯成 SQL 語(yǔ)句執(zhí)行。
這種需要數(shù)據(jù)時(shí)才會(huì)查詢(xún)的行為,看起來(lái)有點(diǎn)像顯式加載,但絕對(duì)不是一回事兒。
結(jié)合示例簡(jiǎn)單的說(shuō),「post1」 只是一個(gè)以 LINQ 表達(dá)式體現(xiàn)的查詢(xún)語(yǔ)句,就好比你編寫(xiě)了一條字符串 SQL 語(yǔ)句。
至于這條語(yǔ)句是否執(zhí)行,取決于下一步操作。比如 ToList 方法,就是這一步查詢(xún)操作。
因?yàn)槭褂?ToList 方法的目的,就是為了獲取數(shù)據(jù)集合,所以 EF Core 才會(huì)執(zhí)行這條查詢(xún)語(yǔ)句。
如果非要給這種行為起一個(gè)名字的話,我覺(jué)得應(yīng)該叫延遲執(zhí)行更合適。
我們?cè)賮?lái)看看,LINQ 方法返回的數(shù)據(jù)類(lèi)型是什么:

「post1」 的數(shù)據(jù)類(lèi)型是泛型的 IOrderedQueryable,它本質(zhì)上是就是一個(gè) IQueryable 類(lèi)型,不過(guò)是具有排序表達(dá)式的 IQueryable。
LINQ 方法往往都有好幾個(gè)重載,比如 OrdeyBy、Where 等方法,它們返回的數(shù)據(jù)類(lèi)型有兩種:IQueryable 和 IEnumerable。
那么這兩種數(shù)據(jù)類(lèi)型究竟有何區(qū)別?我們做個(gè)試驗(yàn):

在這個(gè)示例中,「posts」 和 「posts1」 的語(yǔ)句,可以視為等效的。
這是因?yàn)椴樵?xún)參數(shù)會(huì)被默認(rèn)為 IQueryable 類(lèi)型,所以最終使用的還是返回 IQueryable 類(lèi)型的重載方法。
在 post2 的語(yǔ)句中,由于 IQueryable 繼承了 IEnumerable,所以可以通過(guò) AsEnumerable 方法,將其返回值轉(zhuǎn)換為 IEnumerable 類(lèi)型。
在這種情況下,只有當(dāng)代碼運(yùn)行到兩個(gè) foreach 遍歷的時(shí)候,才會(huì)真正的執(zhí)行語(yǔ)句。
接下來(lái),再看這個(gè)示例:

這個(gè)示例與前面一個(gè)示例的不同之處在于,「posts1」 查詢(xún)語(yǔ)句的 LINQ 方法在 AsEnumerable 之后才調(diào)用。
運(yùn)行程序,觀察控制臺(tái)日志:

此時(shí)我們終于發(fā)現(xiàn)了不同,兩條 SQL 語(yǔ)句不同,第一條 SQL 語(yǔ)句比第二條 SQL 語(yǔ)句多了一個(gè)分頁(yè)操作。
IQueryable 在查詢(xún)的時(shí)候,會(huì)應(yīng)用所有 LINQ 方法,并且生成一個(gè)完整的 SQL 查詢(xún)語(yǔ)句,去數(shù)據(jù)庫(kù)執(zhí)行并獲取符合查詢(xún)語(yǔ)句的數(shù)據(jù);
而當(dāng)我們使用 IEnumerable 時(shí),則會(huì)生成一條將所有的數(shù)據(jù)查詢(xún)出來(lái)的 SQL 語(yǔ)句,而后在內(nèi)存中再對(duì)數(shù)據(jù)進(jìn)行處理,最終獲得符合查詢(xún)語(yǔ)句的數(shù)據(jù)。
在不使用 AsEnumerable 的情況下,默認(rèn)都是采用 IQueryable 進(jìn)行查詢(xún)。
那么我們什么時(shí)候應(yīng)該使用 AsEnumerable ?
使用 「AsEnumerable」 的查詢(xún),稱(chēng)為客戶(hù)端查詢(xún)。
比如,當(dāng)你想在客戶(hù)端完成篩選或者聚合等操作的時(shí)候,可以選擇轉(zhuǎn)換成 IEnumerable 進(jìn)行查詢(xún)。
雖然這增大了數(shù)據(jù)庫(kù)查詢(xún)壓力和IO壓力,但因?yàn)閷⒂?jì)算轉(zhuǎn)移到了客戶(hù)端,所以也相應(yīng)的減輕了數(shù)據(jù)庫(kù)的計(jì)算壓力。
簡(jiǎn)單來(lái)說(shuō)是,這是一種用 I/O 資源換計(jì)算資源的行為。
