這個做法在介紹 IEnumerable 與 IQueryable 兩者背後的運作機制,這兩個類型在前幾個做法經常提到,它們專注的方面與運作的效率也差很多。

首先下面這種是用 IQueryable 的寫法,它背後的是使用 LINQ to SQL 機制,它會把這兩個語句整理在同一個 expression tree, 最後會翻譯成 SQL 語法傳遞給資料庫運行,所以在進行排序工作的時候是在遠端排序的。

var q = from c in dbContext.Customers
        where c.City == "London"
        select c;
        
 var finalAnswer = from c in q
                  orderby c.Name
                  select c;

另外下面這種是用將上面的程式碼轉成 IEnumerable 的寫法,它背後的是使用 LINQ to Objects 機制,它會把數據保留在本地記憶體內, 所以在進行排序工作的時候其實是在本地進行排序的。

var q = (from c in dbContext.Customers
         where c.City == "London"
         select c).AsEnumerable();
         
 var finalAnswer = from c in q
                  orderby c.Name
                  select c;

另外之前也有提到 LINQ to SQL 背後是各種不同的 provider 來進行轉換工作,所以有些時候沒辦法把 C# 的 LINQ 語法轉換成 SQL 的版本, 所以這種時候就只能轉換成 Enumerable 來處理即可。

void Main()
{
	private bool isValidProduct(Product p) => p.ProductName.LastIndexOf('C') == 0;
	
	// This works:
	var q1 = from p in dbContext.Products.AsEnumerable()
			   where isValidProduct(p)
			   select p;
			   
	// This throws an exception when you enumerate the collection.
	var q2 = from p in dbContext.Products
			where isValidProduct(p)
			select p;
}

所以關鍵的差異也很好懂,IQueryable 會把所有查詢語句整理成一個語句之後再丟給資料庫查詢, IEnumerable 則是操作記憶體內的物件跟資料庫其實沒有關係,所以如果在不對的場合使用錯誤的寫法那會大大影響執行效率。

例如在這個例子中把 where 子句移出 IQueryable 之外,這樣會導致所有資料都讀出來並轉換成 Enumerable 最後才在記憶體進行過濾與排序工作, 這樣既不能利用更快的資料庫搜尋機制,也浪費許多記憶體和傳輸流量。

var q = (from c in dbContext.Customers
		 select c).AsEnumerable();

var finalAnswer = from c in q
				  where c.City == "London"
				  orderby c.Name
				  select c;

另外我們可以使用 AsQueryable 方法將一般的 Enumerable 轉換成 Queryable,這樣不管傳入的集合本身是否為 IQueryable 還是 IEnumerable 都可以當作下面這個方法的數據源。

public static IEnumerable<Product> ValidProducts(this IEnumerable<Product> products) =>
	from p in products.AsQueryable()
	where p.ProductName.LastIndexOf('C') == 0
	select p;

Summary

IEnumerable 與 IQueryable 寫起來與看起來基本都相同,但背後運作的機制卻差很多只要記得 IQueryable 背後是透過 provider 進行各種 不同格式的輸出,IEnumerable 則是本地記憶體操作,特別是在使用 EFCore 的時候很有可能沒有注意到目前是 IEnumerable 還是 IQueryable, 一不小心就有可能影響效能。