簡述Linq中.ToList(), .AsEnumerable(), AsQueryable()的區別和用法
標簽: blog
這3個方法的功能完全不同, 應按照具體業務場景使用.
AsQueryable()
lazy load 特性
以下是一段最常見的代碼,
var products = db.Product.where(p => p.Type == "food").select(p => new { p.Id, p.Name, p.CreateTime});
products 的類型為 IQueryable<T>
, 因此不用加 .AsQueryable()
. 語句執行后不會立刻查詢數據庫, 而是在迭代使用 products 時才會查數據庫, 具有 lazy load 的特性, 按需查數據庫可提高程序效率.
迭代時上面的代碼生成的 sql 語句類似:
select Id, Name, CreateTime from Product where Type = 'food'
對 products 再次使用 IQueryable 操作, 編譯器會把結果合並為1條 sql 語句, 如下,
var products = db.Product.where(p => p.Type == "food").select(p => new { p.Id, p.Name, p.CreateTime});
var orderedProducts = products.OrderBy(p => p.CreateTime);
迭代時生成的 sql 語句類似:
select Id, Name, CreateTime from Product where Type = 'food' order by CreateTime
顯式調用 AsQueryable()?
如果對沒有繼承 IQueryable 接口的類型使用 AsQueryable(), 會轉換類型, 稍微消耗資源, 如下,
int[] array = new { 1, 2, 4, 5};
var enumArray = array.AsQueryable();
因為 Array 只繼承了 IEnumerable, 沒有繼承 IQueryable, 所以會發生類型轉換. 當然其中的泛型實現沒有拆箱, 對性能影響不大. 但是沒有必要, 因為 IQueryable 沒有聲明任何新方法.
其他有用的情況我暫時還沒碰到, 請大家指教.
IQueryable的限制
此外 IQueryable 有諸多限制, 只支持數據庫查詢語法, 無法支持 Linq to object 的操作, 如以下2段代碼會在運行時出錯,
var products = db.Product
.Where(p => p.Type == "food")
.Select(p => new { p.Id, p.Name, p.CreateTime.Date});
// 如果改成 .Select(p => new { p.Id, p.Name, DbFunction.TruncateDate(p.CreateTime)})
// 就能正常運行.
var products = db.Product
.Where(p => p.Type == "food")
.Select(p => MyMethod(p));
.Select() 的返回類型為 IQueryable.
第1段代碼, 我認為 IQueryable 還不夠智能, 無法把 p.CreateTime.Date 轉換為 sql 相關的 function, 而使用 DbFunctions 要求使用者了解同時熟悉 linq to entity 及 sql 的內置方法.
第2段代碼, 生成 的 sql 無法返回 MyMethod 類型, 是可以理解的.
但是, 對代碼加了 AsEnumerable() 后運行正常, 因為 IEnumerable 支持 Linq to object 的操作.
AsEnumerable()
同樣支持 lazy load, 但不要濫用
同樣是延遲查詢, 與 AsQueryable() 區別是, 迭代時遇到 AsEnumerable() 會先進行 sql 查詢, 所以對已查出來的結果當然能進行 Linq to object 操作.
但是, 千萬不要為了方便而濫用 AsEnumerable(), 可能會嚴重消耗資源, 如下代碼,
var products = db.Product.AsEnumerable()
.Select(p => new {p.Id, p.Name, p.CreateTime.Date});
上面的代碼在查詢時會把整個Product表的結果存放進內存, 然后進行 .Select 查詢!!!
當我不熟悉 DbFunction 或者 要用自定義返回類型應該怎么辦
正確的做法應該如下,
var products = db.Product
.Select(p => new {p.Id, p.Name, p.CreateTime})
.AsEnumerable()
.Select(p => MyMethod(p));
AsEnumerable()的限制
如果你寫了以下代碼,
var products = db.Product
.Select(p => new {p.Id, p.Name, p.CreateTime})
.AsEnumerable()
.Select(p => new {p.Id, p.Name, p.CreateTime.ToString()});
.AsQueryable()
.Where(p=> p.Name == "xxx");
那么最后的 .Where()永遠不會執行!!!
因為在使用 AsQueryable().Where() 要求 Linq to entity 進行數據庫查詢, 但是第一次 AsEnumerable() 時已經進行了數據庫查詢並且斷開連接, 並且查詢結果已經作為實實在在的 object, 對 object 不可能再次使用 AsQueryable().Where() 疊加數據庫查詢!
ToList()
調用 ToList() 會立刻查詢並保存結果, 而不會等到迭代時才查詢. 作用和 lazy load 是對立的.
在需要得到完整結果后, 再處理的場景, 需要使用 ToList().
例如, 在返回mvc ActionResult 的時候, 要先使用 ToList(), 再作為 model 傳給 view. 不知道 mvc 是出於什么考慮不支持在生成 html 的時候lazy load, 請大家指教!
最后
使用 EF 也蠻久了, 確實方便, 但是不熟悉其中原理的話不免要踩坑, 目前正在掙扎從坑里面爬出來lol.
有錯的地方請大家指出, 歡迎拍磚.
參考 StackOverflow: What's the difference(s) between .ToList(), .AsEnumerable(), AsQueryable()?
勘誤
看到評論指出 .AsEnumerable().AsQueryable().Where(...)
, Where
條件也會生效. 確實文中這一部分描述有誤.
實際情況是, 在迭代時遇到AsEnumerable()
會先把數據裝入內存, 之后調用AsQueryable().Where()
, 但這時候底層的類型是IEmumerable
而不是IQueryable
, 所以框架會使用IEnumeralbe.Where()
, 因此能夠過濾數據. 然而, 當繼續使用.Include()
, .Where(i => DbFunctions...)
等IQueryable
獨有的方法時, 框架會使用空實現, 沒有效果.