深入理解IEnumerable和IQueryable兩接口的區別


from:http://blog.csdn.net/ydm19891101/article/details/50969323

無論是在ado.net EF或者是在其他的Linq使用中,我們經常會碰到兩個重要的靜態類Enumerable、Queryable,他們在System.Linq命名空間下。那么這兩個類是如何定義的,又是來做什么用的呢?特別是Queryable類,它和EF的延遲加載技術有什么聯系呢?

好,帶着上面的問題開始我們今天的學習。

首先介紹兩個類的定義

(1)Enumerable類,對繼承了IEnumerable<T>接口的集合進行擴展;

(2)Queryable類,針對繼承了IQueryable<T>接口的集合進行擴展。

在繼續學習之前,我們先來看一下EF中定義的實體集DbSet<T>

 

 

通過上面的截圖我們可以看到 DbSet<T>實現了IQueryable<T>、IEnumerable<T>接口。

與上面的兩句話結合起來意思就是可以通過兩個靜態類對DbSet<T>進行擴展操作。其實查看兩個類的源碼可以知道,這兩個類對實現了IQueryable<T>、IEnumerable<T>接口的集合進行了很多方法的擴展。

 

 

可能你還不知道如何進行擴展方法的定義以及操作,沒事兒,請參考另外一篇文章:C#擴展方法的理解

但是那么的擴展方法不都是我們需要的,我們在ado.net EF中最常用的就是擴展的Where方法。

兩個類中Where擴展方法的定義分別如下

(1)Enumerable類

 

[csharp]  view plain  copy
 
  1. public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);  
  2. public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);  

觀察Where方法,可以看到第一個參數是實現了IEnumable接口的類,第二個參數是一個Func<T>委托類型

 

(2)Queryable類

 

[csharp]  view plain  copy
 
  1. public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);  
  2. public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate);  

觀察Where方法,可以看到第一個參數是實現了IEnumable接口的類,第二個參數是一個Expresssion類型
很顯然,兩個類擴展的Where方法是不同的,那具體有什么不同呢?那么這種不同又導致什么結果呢?

 

OK,帶着疑問繼續往下學習。

為了便於大家好學習,在這里,我們編寫一段代碼,通過監視工具查看兩者的區別。

先上代碼

 

[csharp]  view plain  copy
 
  1. private void Form1_Load(object sender, EventArgs e)  
  2. {  
  3.     using (DemoContext context = new DemoContext())  
  4.     {  
  5.         var customer = context.cunstomer.Where(c => c.Name == "牡丹");  
  6.         foreach (var item in customer)  
  7.         {  
  8.             MessageBox.Show(item.Id.ToString());  
  9.         }  
  10.     }  
  11. }  

 

至於代碼中的上下文定義以及實體集大家不必糾結,我們在這里要透過表象看本質。在上面的程序中添加斷點,同時啟動sql server profiler監視工具,運行程序。

 

程序會在斷點處停下來,如下所示

 

 

上面只是到點斷點處,當然斷點處的語句還有執行,繼續單步執行。

 

 

執行過斷點處所在的語句,觀察監視工具還是什么都沒有。

咦,是不是出什么問題了呢?為什么沒有查詢語句執行呢?真的是監視工具出問題了嗎?

繼續單步調試

 

 

咦,這個時候怎么出現sql查詢語句了。很奇怪吧,這就是ado.net EF的延遲加載技術,這里面很重要的一部分就是通過IQueryable接口實現的(具體我們放到最后再說)。

講過了Queryable類的Where方法,接下來我們再來看一下Enumable類的Where方法。

修改上面的代碼如下所示

 

[csharp]  view plain  copy
 
  1. private void Form1_Load(object sender, EventArgs e)  
  2. {  
  3.     using (DemoContext context = new DemoContext())  
  4.     {  
  5.         var customer = context.cunstomer.Where(c => c.Name == "牡丹").AsEnumerable();  
  6.         foreach (var item in customer)  
  7.         {  
  8.             MessageBox.Show(item.Id.ToString());  
  9.         }  
  10.     }  
  11. }  

 

 

同樣是打開監視工具,添加斷點,運行程序

 

 

單步調試,繼續運行

 

 

執行過斷點所在的語句及執行了查詢語句。

關於上面的兩個測試總結如下。

 

(1)所有對於IEnumerable的過濾,排序等操作,都是在內存中發生的。也就是說數據已經從數據庫中獲取到了內存中,只是在內存中進行過濾和排序操作。

(2)所有對於IQueryable的過濾,排序等操作,只有在數據真正用到的時候才會到數據庫中查詢。這也是Linq的延遲加載核心所在。

那最后一個問題,IQueryable接口為何那么特殊呢?

觀察它的定義

 

[csharp]  view plain  copy
 
  1. // 摘要:  
  2. //     提供對未指定數據類型的特定數據源的查詢進行計算的功能。  
  3. public interface IQueryable : IEnumerable  
  4. {  
  5.     // 摘要:  
  6.     //     獲取在執行與 System.Linq.IQueryable 的此實例關聯的表達式樹時返回的元素的類型。  
  7.     //  
  8.     // 返回結果:  
  9.     //     一個 System.Type,表示在執行與之關聯的表達式樹時返回的元素的類型。  
  10.     Type ElementType { get; }  
  11.     //  
  12.     // 摘要:  
  13.     //     獲取與 System.Linq.IQueryable 的實例關聯的表達式樹。  
  14.     //  
  15.     // 返回結果:  
  16.     //     與 System.Linq.IQueryable 的此實例關聯的 System.Linq.Expressions.Expression。  
  17.     Expression Expression { get; }  
  18.     //  
  19.     // 摘要:  
  20.     //     獲取與此數據源關聯的查詢提供程序。  
  21.     //  
  22.     // 返回結果:  
  23.     //     與此數據源關聯的 System.Linq.IQueryProvider。  
  24.     IQueryProvider Provider { get; }  
  25. }  

 

該接口有三個特殊的屬性,具體內容代碼已經介紹了,那查詢時具體又是如何執行呢?

答案是該接口會把查詢表達式先緩存到表達式樹中,只有當真正遍歷發生的時候,才會由IQueryProvider解析表達式樹,生成sql語句執行數據庫查詢操作。

哎呀,寫到現在終於差不多快寫完了。

上面介紹了兩個接口的區別與聯系,具體使用哪種就看自己的項目需求了。

最后補充一下List.Where()方法,還是以代碼說明。

 

[csharp]  view plain  copy
 
  1. List<string> fruits =  
  2.     new List<string> { "apple", "passionfruit", "banana", "mango",   
  3.                     "orange", "blueberry", "grape", "strawberry" };  
  4.   
  5. IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);  
  6.   
  7. foreach (string fruit in query)  
  8. {  
  9.     Console.WriteLine(fruit);  

 

 

查看List<T>的定義,如下圖所示



它也是繼承了IEnumerable接口,因此,他也不存在延遲加載。
OK,到此,所有工作完成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM