[筆記] IEnumerable v.s IQueryable

  • 11181
  • 0
  • 2019-08-24

先前一直不是很明白IEnumerable 和 IQueryable的差別

只知道IQueryable繼承自IEnumerable, 

IEnumerable是把整個資料撈回來存在記憶體再去篩選,

IQueryable是條件都串好再去query資料, 遇到資料量大不管怎樣轉成IQueryable的型別就對了....

先了解何謂迭代器(Iterator)

迭代器(Iterator)是一個巡迴容器, 常用的像是List, Dictionary....等這類聚合物件, 都可以透過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() 方法,則是再次將指標移動到資料集合中,第一個元素之前的位置

image

所以具有可被列舉的特性, 就必須要繼承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) }
			));
}
所以關鍵點在於目前的物件是否有實作 IQueryable<T>,才能執行 IQueryable<T> 的 Expression 與 Provider。

 

釐清底下兩段:

// 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