不管是在ado.net EF或者是在其餘的Linq使用中,咱們常常會碰到兩個重要的靜態類Enumerable、Queryable,他們在System.Linq命名空間下。那麼這兩個類是如何定義的,又是來作什麼用的呢?特別是Queryable類,它和EF的延遲加載技術有什麼聯繫呢?html
好,帶着上面的問題開始咱們今天的學習。sql
首先介紹兩個類的定義數據庫
(1)Enumerable類,對繼承了IEnumerable<T>接口的集合進行擴展;緩存
(2)Queryable類,針對繼承了IQueryable<T>接口的集合進行擴展。app
在繼續學習以前,咱們先來看一下EF中定義的實體集DbSet<T>ide
經過上面的截圖咱們能夠看到 DbSet<T>實現了IQueryable<T>、IEnumerable<T>接口。工具
與上面的兩句話結合起來意思就是能夠經過兩個靜態類對DbSet<T>進行擴展操做。其實查看兩個類的源碼能夠知道,這兩個類對實現了IQueryable<T>、IEnumerable<T>接口的集合進行了不少方法的擴展。學習
可能你還不知道如何進行擴展方法的定義以及操做,沒事兒,請參考另一篇文章:C#擴展方法的理解測試
可是那麼的擴展方法不都是咱們須要的,咱們在ado.net EF中最經常使用的就是擴展的Where方法。ui
兩個類中Where擴展方法的定義分別以下
(1)Enumerable類
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate); public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
觀察Where方法,能夠看到第一個參數是實現了IEnumable接口的類,第二個參數是一個Func<T>委託類型
(2)Queryable類
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate); public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate);
觀察Where方法,能夠看到第一個參數是實現了IEnumable接口的類,第二個參數是一個Expresssion類型
很顯然,兩個類擴展的Where方法是不一樣的,那具體有什麼不一樣呢?那麼這種不一樣又致使什麼結果呢?
OK,帶着疑問繼續往下學習。
爲了便於你們好學習,在這裏,咱們編寫一段代碼,經過監視工具查看二者的區別。
先上代碼
private void Form1_Load(object sender, EventArgs e) { using (DemoContext context = new DemoContext()) { var customer = context.cunstomer.Where(c => c.Name == "牡丹"); foreach (var item in customer) { MessageBox.Show(item.Id.ToString()); } } }
至於代碼中的上下文定義以及實體集你們沒必要糾結,咱們在這裏要透過表象看本質。在上面的程序中添加斷點,同時啓動sql server profiler監視工具,運行程序。
執行過斷點處所在的語句,觀察監視工具仍是什麼都沒有。
咦,是否是出什麼問題了呢?爲何沒有查詢語句執行呢?真的是監視工具出問題了嗎?
繼續單步調試
咦,這個時候怎麼出現sql查詢語句了。很奇怪吧,這就是ado.net EF的延遲加載技術,這裏面很重要的一部分就是經過IQueryable接口實現的(具體咱們放到最後再說)。
講過了Queryable類的Where方法,接下來咱們再來看一下Enumable類的Where方法。
修改上面的代碼以下所示
private void Form1_Load(object sender, EventArgs e) { using (DemoContext context = new DemoContext()) { var customer = context.cunstomer.Where(c => c.Name == "牡丹").AsEnumerable(); foreach (var item in customer) { MessageBox.Show(item.Id.ToString()); } } }
一樣是打開監視工具,添加斷點,運行程序
單步調試,繼續運行
執行過斷點所在的語句及執行了查詢語句。
關於上面的兩個測試總結以下。
(1)全部對於IEnumerable的過濾,排序等操做,都是在內存中發生的。也就是說數據已經從數據庫中獲取到了內存中,只是在內存中進行過濾和排序操做。
(2)全部對於IQueryable的過濾,排序等操做,只有在數據真正用到的時候纔會到數據庫中查詢。這也是Linq的延遲加載核心所在。
那最後一個問題,IQueryable接口爲什麼那麼特殊呢?
觀察它的定義
// 摘要: // 提供對未指定數據類型的特定數據源的查詢進行計算的功能。 public interface IQueryable : IEnumerable { // 摘要: // 獲取在執行與 System.Linq.IQueryable 的此實例關聯的表達式樹時返回的元素的類型。 // // 返回結果: // 一個 System.Type,表示在執行與之關聯的表達式樹時返回的元素的類型。 Type ElementType { get; } // // 摘要: // 獲取與 System.Linq.IQueryable 的實例關聯的表達式樹。 // // 返回結果: // 與 System.Linq.IQueryable 的此實例關聯的 System.Linq.Expressions.Expression。 Expression Expression { get; } // // 摘要: // 獲取與此數據源關聯的查詢提供程序。 // // 返回結果: // 與此數據源關聯的 System.Linq.IQueryProvider。 IQueryProvider Provider { get; } }
該接口有三個特殊的屬性,具體內容代碼已經介紹了,那查詢時具體又是如何執行呢?
答案是該接口會把查詢表達式先緩存到表達式樹中,只有當真正遍歷發生的時候,纔會由IQueryProvider解析表達式樹,生成sql語句執行數據庫查詢操做。
哎呀,寫到如今終於差很少快寫完了。
上面介紹了兩個接口的區別與聯繫,具體使用哪一種就看本身的項目需求了。
最後補充一下List.Where()方法,仍是以代碼說明。
List<string> fruits = new List<string> { "apple", "passionfruit", "banana", "mango", "orange", "blueberry", "grape", "strawberry" }; IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6); foreach (string fruit in query) { Console.WriteLine(fruit);
查看List<T>的定義,以下圖所示
它也是繼承了IEnumerable接口,所以,他也不存在延遲加載。