先前一直不是很明白IEnumerable 和 IQueryable的差別
只知道IQueryable繼承自IEnumerable,
IEnumerable是把整個資料撈回來存在記憶體再去篩選,
IQueryable是條件都串好再去query資料, 遇到資料量大不管怎樣轉成IQueryable的型別就對了....
先了解何謂迭代器(Iterator)
而IEnumerable、IEnumerable<T>這個介面只有一個方法正是提供IEnumerator這種迭代器
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
.Net 對「是否可被列舉」這件事的定義上,就是認定是否具備取得「列舉器」(Enumerator) 的能力
取得的IEnumerator包含兩個方法一個屬性
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
方法和屬性作用如下:
指標一開始的位置,是在第一個元素之前,也就是不指向任何資料元素。
Current 屬性,即是回傳目前指標指向的值的內容;MoveNext() 方法則是將指標往下移一個位置,並回傳 true/false 來告知,指標向下移動是否成功
Reset() 方法,則是再次將指標移動到資料集合中,第一個元素之前的位置
所以具有可被列舉的特性, 就必須要繼承IEnumerable、IEnumerable<T>
再來看IQueryable, 繼承自IEnumerable, 所以也有GetEnumerator()這個方法
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
public interface IQueryable<out T> : IEnumerable<T>, IQueryable
{
}
public interface IQueryProvider
{
IQueryable CreateQuery(Expression expression);
IQueryable<TElement> CreateQuery<TElement>(Expression expression);
object Execute(Expression expression);
TResult Execute<TResult>(Expression expression);
}
回到最原始出發點: 到底IEnumerable和IQueryable有什麼差別? 在用entity framework時不懂啊~
隨便取一個範例如下, SchoolContext 繼承自 DbContext, 其中類型DbSet<T>對應到資料庫的table,
預設 DbSet<T> 就實作 IQueryable<T>
using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ContosoUniversity.DAL
{
public class SchoolContext : DbContext
{
public SchoolContext() : base("SchoolContext")
{
}
public DbSet<Orders> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
以 var list = db.Orders.Where(x => x.OrderId == 5).ToList();來看, 會優先使用衍生繼承的實作類別IQueryable的Where 擴充方法
主要就是為了保留 Expression 與 Provider ,
在呼叫 ToList() 的時候,會執行 GetEnumerator(),
而現在實際的物件是 InternalDbSet (實作了抽象類別 DbSet),
InternalDbSet 會實作 GetEnumerator 執行的方式,
裡面就會用 IQueryable<T> 的 Expression 與 Provider 來轉譯成 SQL 執行查詢取得資料
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) {
if (source == null)
throw Error.ArgumentNull("source");
if (predicate == null)
throw Error.ArgumentNull("predicate");
return source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
GetMethodInfo(Queryable.Where, source, predicate),
new Expression[] { source.Expression, Expression.Quote(predicate) }
));
}
釐清底下兩段:
// part1
var query1 = db.Orders.Where(x => x.OrderId != 5);
var query2 = query1.AsEnumerable();
var query3 = query2.AsQueryable();
var list = query3.Where(x => x.CreateDate < DateTime.Now).ToList();
// part2
var query1 = db.Orders.Where(x => x.OrderId != 5);
var query2 = query1.AsEnumerable();
var query3 = query2.Where(x=>x.CreateDate < DateTime.Now).AsQueryable();
var list = query3.ToList();
不同點在assign給query3時
part1 query2透過AsQueryable()轉為IQueryable, 在指定給list時呼叫IQueryable的Where, 呼叫ToList()時會執行 GetEnumerator(),
part2 query2呼叫Where(此為IEnumerable的方法, 此時會直接發請求給db取得資料放至記憶體, 再加入x.CreateDate < DateTime.Now的條件), 再轉為IQueryable
轉譯後的SQL
/*part 1*/
SELECT [x].[OrderId], [x].[CreateDate]
FROM [Orders] AS [x]
WHERE ([x].[OrderId] <> 5) AND ([x].[CreateDate] < GETDATE());
/*part 2*/
SELECT [x].[OrderId], [x].[CreateDate]
FROM [Orders] AS [x]
WHERE [x].[OrderId] <> 5;
結論:
先前一直看不懂到底什麼時候會組SQL語法,
看了幾遍也看不懂.....
原來還是要從最基本的Iterator及IQueryable與IEnumerable的差別了解
引用參考:
http://vegeee-csharp.blogspot.com/2017/02/c-ienumerableienumerator-ienumerable.html
https://dotblogs.com.tw/jgame2012/2016/10/16/173531
https://www.huanlintalk.com/2016/07/learning-linq-1.html
https://dev.twsiyuan.com/2016/03/csharp-ienumerable-ienumerator-and-yield-return.html