《C# 本事》摘錄:LINQ (1)
文章目錄
改來改去,LINQ 這一章的骨架與呈現方式終於大致底定。如果我正要開始學習 LINQ,我會希望有這樣的書可以參考。我打算把其中部分內容摘錄一些上來,一方面替這個快要荒蕪的部落格加一些東西,另一方面也是為這本書打個廣告。
我想,這系列的第一篇就從〈LINQ 提供者〉這一節開始節錄吧,其中包括 IEnumerable<T> 與 IQueryable<T> 的差別,這是面試時經常問到的問題。
LINQ 提供者
經過前面的介紹,你已經知道,.NET Framework 為 LINQ 查詢語法定義了一套基本的查詢操作(例如篩選、排序等等),這套基本的查詢操作稱為「標準查詢運算子」。那麼,對於每一種不同類型的資料來源,都得要有人去實作這些運算子(你可以理解為實作一組底層的 API),我們才能用 LINQ 語法去查詢特定類型的資料。那些針對特定資料類型所實作的一組 LINQ API,便叫做「LINQ 提供者」(LINQ providers)。.NET Framework 內建了數個提供者,其中最常用到的兩個 LINQ 提供者是:
另外還有 LINQ to XML、LINQ to OData 等等,你也可以在網路上找到一些第三方、開放原始碼的 LINQ 提供者。有了這些 LINQ 提供者,我們就能使用同一種語法來查詢不同來源的資料。
IEnumerable<T> vs. IQueryable<T>
介面 IEnumerable<T> 可說是 LINQ to Objects 的靈魂,因為其查詢運算子所操作的對象都是此類型的物件。另一方面,許多資料庫提供者則是使用 IQueryable<T>,亦即查詢的資料來源是實作了 IQueryable<T> 的物件。這兩種介面有什麼差異呢?
底下是 IQueryable<T> 與其相關型別的定義:
你可以看到, IQueryable<T> 繼承自 IQueryable 和 IEnumerable<T>,所以 IEnumerable<T> 能做的, IQueryable<T> 也都能做。就像 System.Linq.Enumerable 靜態類別為 IEnumerable<T> 提供了一組擴充方法(所謂的標準查詢運算子),在 System.Linq.Queryable 靜態類別當中也同樣為 IQueryable<T> 提供了一組標準查詢運算子。
雖然 IQueryable<T> 本身並未增加任何新的方法,但它的基礎型別 IQueryable 有兩個新成員:
觀察 IQueryProvider 介面所定義的方法,可以發現它操作的對象是 Expression 物件。而我們又可以從 IQueryable<T> 的擴充方法所接受的參數看到 Expression,例如 Where() 方法:
這是因為,IQueryable<T> 的擴充方法其實都是把真正的查詢工作交給背後的 IQueryProvider 物件來執行。
這裡有一個和 IEnumerable<T> 明顯不同的地方:使用 IQueryable<T> 的擴充方法時,傳入的 lambda 表示式會被編譯器轉譯成 Expression 類型的物件,而使用 IEnumerable<T> 的擴充方法時,傳入的 lambda 則被單純轉譯成委派。試將底下的 IEnumerable<T> 的 Where() 方法跟剛才的 IQueryable<T> 的 Where() 比較,便可發現兩者的差異。
有些 LINQ 提供者需要使用 IQueryable<T> (而不是 IEumerable<T>),通常就是因為它們需要保存完整的表示式樹( expression tree),以便將 LINQ 查詢轉換成底層資料來源所使用的查詢語法(例如 SQL)。
使用 IQueryable<T> 的好處是,它是針對查詢結果來列舉元素,亦即在遠端(資料庫)執行完查詢之後,才去巡覽其中的元素。另一方面,使用 IEumerable<T> 的查詢方法時,它需要巡覽集合中的全部元素,以便對各元素進行篩選或排序等操作。也因為這個緣故,我們說 IEumerable<T> 適用於查詢本地記憶體中的( in-memory)集合,而 IQueryable<T> 適合用於查詢遠端資料的場合。
容我借用網路上一個影片當中的例子來補充說明。範例程式碼如下:
這裡使用了 Entity Framework 來查詢資料庫。當程式執行第 3 行敘述時,背後產生的 SQL 指令是選取整個資料表,而沒有事先篩選 Empid == 2 的員工資料。篩選的工作是後來在用戶端這邊才處理的。
如果把 IEumerable<T> 換成 IQueryable<T>:
背後產生的 SQL 指令則有包含 where 子句,亦即在資料庫端就先篩選好資料,然後才傳回用戶端。這種做法當然更又效率。
影片中使用了兩張圖來表現兩者的差異,我依樣畫葫蘆,附在下方。
下回預告:LINQ 的延遲執行與重複取值
工商時間:目前 LINQ 這一章初步完成的內容如下圖
這本書目前在下列平台上架:
Happy reading!
我想,這系列的第一篇就從〈LINQ 提供者〉這一節開始節錄吧,其中包括 IEnumerable<T> 與 IQueryable<T> 的差別,這是面試時經常問到的問題。
註:由於是從電子書上面轉貼至網頁,有些文字的格式(例如程式碼)可能跟書裡面呈現的不一樣。此外,電子書的內容仍在持續更新,所以一定會比這裡摘錄的文章還要新(當然也更完整)。以下開始摘錄內容...
LINQ 提供者
經過前面的介紹,你已經知道,.NET Framework 為 LINQ 查詢語法定義了一套基本的查詢操作(例如篩選、排序等等),這套基本的查詢操作稱為「標準查詢運算子」。那麼,對於每一種不同類型的資料來源,都得要有人去實作這些運算子(你可以理解為實作一組底層的 API),我們才能用 LINQ 語法去查詢特定類型的資料。那些針對特定資料類型所實作的一組 LINQ API,便叫做「LINQ 提供者」(LINQ providers)。.NET Framework 內建了數個提供者,其中最常用到的兩個 LINQ 提供者是:
- LINQ to Objects:
用來對序列/集合物件進行查詢。它是由 System.Linq.Enumerable 類別所提供的擴充方法所構成的一組 API,擴充的對象是 IEnumerable<T>。因此,只要是有實作 IEnumerable<T> 的物件,都可以用這組擴充方法。由於這組 API 所查詢的對象是本地(local)記憶體中的集合物件,故這類查詢又稱之為「本地查詢」。 - LINQ to Entities:
用於查詢資料庫。此提供者是使用 IQuerable<T> 介面,而我們可以透過 System.Linq.Queryable 類別所實作的運算子(擴充方法)來進行查詢。
另外還有 LINQ to XML、LINQ to OData 等等,你也可以在網路上找到一些第三方、開放原始碼的 LINQ 提供者。有了這些 LINQ 提供者,我們就能使用同一種語法來查詢不同來源的資料。
註:本章內容幾乎都是屬於 LINQ to Objects 的範疇。
IEnumerable<T> vs. IQueryable<T>
介面 IEnumerable<T> 可說是 LINQ to Objects 的靈魂,因為其查詢運算子所操作的對象都是此類型的物件。另一方面,許多資料庫提供者則是使用 IQueryable<T>,亦即查詢的資料來源是實作了 IQueryable<T> 的物件。這兩種介面有什麼差異呢?
底下是 IQueryable<T> 與其相關型別的定義:
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);
}
你可以看到, IQueryable<T> 繼承自 IQueryable 和 IEnumerable<T>,所以 IEnumerable<T> 能做的, IQueryable<T> 也都能做。就像 System.Linq.Enumerable 靜態類別為 IEnumerable<T> 提供了一組擴充方法(所謂的標準查詢運算子),在 System.Linq.Queryable 靜態類別當中也同樣為 IQueryable<T> 提供了一組標準查詢運算子。
雖然 IQueryable<T> 本身並未增加任何新的方法,但它的基礎型別 IQueryable 有兩個新成員:
- IQueryProvider 物件。此物件的角色是查詢提供者,它會負責把 LINQ 查詢表示式轉 換成底層資料來源的查詢語法。
- Expression 物件。此物件保存了查詢表示式樹( expression tree)。
觀察 IQueryProvider 介面所定義的方法,可以發現它操作的對象是 Expression 物件。而我們又可以從 IQueryable<T> 的擴充方法所接受的參數看到 Expression,例如 Where() 方法:
public static class Queryable
{
public static IQueryable<TSource> Where<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate) // 傳入 Expression 物件
...
}
這是因為,IQueryable<T> 的擴充方法其實都是把真正的查詢工作交給背後的 IQueryProvider 物件來執行。
這裡有一個和 IEnumerable<T> 明顯不同的地方:使用 IQueryable<T> 的擴充方法時,傳入的 lambda 表示式會被編譯器轉譯成 Expression 類型的物件,而使用 IEnumerable<T> 的擴充方法時,傳入的 lambda 則被單純轉譯成委派。試將底下的 IEnumerable<T> 的 Where() 方法跟剛才的 IQueryable<T> 的 Where() 比較,便可發現兩者的差異。
Note: Lambda 表示式可以被轉換成委派(delegate)或表示式樹(expression tree)。
public static class Enumerable
{
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate) // 傳入委派
...
}
有些 LINQ 提供者需要使用 IQueryable<T> (而不是 IEumerable<T>),通常就是因為它們需要保存完整的表示式樹( expression tree),以便將 LINQ 查詢轉換成底層資料來源所使用的查詢語法(例如 SQL)。
使用 IQueryable<T> 的好處是,它是針對查詢結果來列舉元素,亦即在遠端(資料庫)執行完查詢之後,才去巡覽其中的元素。另一方面,使用 IEumerable<T> 的查詢方法時,它需要巡覽集合中的全部元素,以便對各元素進行篩選或排序等操作。也因為這個緣故,我們說 IEumerable<T> 適用於查詢本地記憶體中的( in-memory)集合,而 IQueryable<T> 適合用於查詢遠端資料的場合。
容我借用網路上一個影片當中的例子來補充說明。範例程式碼如下:
EmpEntities ent = new EmpEntities();
IEnumerable<Employee> emp = ent.Employees; // 透過 Entity Framework 載入員工資料
IEnumerable<Employee> temp = emp.Where(x => x.Empid == 2).ToList<Employee>();
這裡使用了 Entity Framework 來查詢資料庫。當程式執行第 3 行敘述時,背後產生的 SQL 指令是選取整個資料表,而沒有事先篩選 Empid == 2 的員工資料。篩選的工作是後來在用戶端這邊才處理的。
如果把 IEumerable<T> 換成 IQueryable<T>:
EmpEntities ent = new EmpEntities();
IQueryable<Employee> emp = ent.Employees;
IQueryable<Employee> temp = emp.Where(x => x.Empid == 2).ToList<Employee>()
背後產生的 SQL 指令則有包含 where 子句,亦即在資料庫端就先篩選好資料,然後才傳回用戶端。這種做法當然更又效率。
影片中使用了兩張圖來表現兩者的差異,我依樣畫葫蘆,附在下方。
下回預告:LINQ 的延遲執行與重複取值
工商時間:目前 LINQ 這一章初步完成的內容如下圖
這本書目前在下列平台上架:
- Leanpub.com (如果是第一次在 leanpub 買書,請參考這篇:在 leanpub.com 買書的步驟)
- Google 圖書
Happy reading!