程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 打造自己的LINQ Provider(中):IQueryable和IQueryProvider

打造自己的LINQ Provider(中):IQueryable和IQueryProvider

編輯:關於.NET

概述

在.NET Framework 3.5中提供了LINQ 支持後,LINQ就以其強大 而優雅的編程方式贏得了開發人員的喜愛,而各種LINQ Provider更是滿天飛, 如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的 趨勢。LINQ本身也提供了很好的擴展性,使得我們可以輕松的編寫屬於自己的 LINQ Provider。

本文為打造自己的LINQ Provider系列文章第二篇,主 要詳細介紹自定義LINQ Provider中兩個最重要的接口IQueryable和 IQueryProvider。

IEnumerable<T>接口

在上一篇打造自己 的LINQ Provider(上):Expression Tree揭秘》一文的最後,我說到了這樣一 句話:需要注意的是LINQ to Objects並不需要任何特定的LINQ Provider,因為 它並不翻譯為表達式目錄樹,帶著這個問題,我們先來看下面這段代碼,查詢的 結果query為IEnumerable<String>類型:

static void Main(string[] args)
{
  List<String> myList = new List<String>() { "a", "ab", "cd", "bd" };
  IEnumerable<String> query = from s in myList
        where s.StartsWith("a")
         select s;
  foreach (String s in query)
   {
    Console.WriteLine(s);
  }
  Console.Read ();
}

這裡將返回兩條結果,如下圖所示:

這裡就有一個問題,為什麼在LINQ to Objects中返回的是 IEnumerable<T>類型的數據而不是IQueryable<T>呢?答案就在本 文的開始,在LINQ to Objects中查詢表達式或者Lambda表達式並不翻譯為表達 式目錄樹,因為LINQ to Objects查詢的都是實現了IEnmerable<T>接口的 數據,所以查詢表達式或者Lambda表達式都可以直接轉換為.NET代碼來執行,無 需再經過轉換為表達式目錄這一步,這也是LINQ to Objects比較特殊的地方, 它不需要特定的LINQ Provider。我們可以看一下IEnumerable<T>接口的 實現,它裡面並沒有Expression和Provider這樣的屬性,如下圖所示:

至於LINQ to Objects中所有的標准查詢操作符都是通過擴展方法來實 現的,它們在抽象類Enumerable中定義,如其中的Where擴展方法如下代碼所示 :

public static class Enumerable
{
  public static IEnumerable<TSource> Where<TSource>(
     this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)
  {
    if (source == null)
    {
      throw Error.ArgumentNull ("source");
    }
    if (predicate == null)
    {
      throw Error.ArgumentNull ("predicate");
    }
    return WhereIterator<TSource>(source, predicate);
  }
   public static IEnumerable<TSource> Where<TSource>(
     this IEnumerable<TSource> source,
     Func<TSource, int, bool> predicate)
  {
    if (source == null)
    {
      throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
      throw Error.ArgumentNull("predicate");
    }
     return WhereIterator<TSource>(source, predicate);
  }
}

注意到這裡方法的參數Func<TSource>系列委托, 而非Expression<Func<TSource>>,在本文的後面,你將看到, IQueryable接口的數據,這些擴展方法的參數都是 Expression<Func<TSource>>,關於它們的區別在上一篇文章我已 經說過了。同樣還有一點需要說明的是,在IEnumerable<T>中提供了一組 擴展方法AsQueryable(),可以用來把一個IEnumerable<T>類型的數據轉 換為IQueryable<T>類型,如下代碼所示:

static void Main(string[] args)
{
  var myList = new List<String>()
        { "a", "ab", "cd", "bd" }.AsQueryable<String>();
  IQueryable<String> query = from s in myList
        where s.StartsWith ("a")
        select s;
  foreach (String s in query)
  {
    Console.WriteLine(s);
  }
  Console.Read();
}

運行這段代碼,雖 然它的輸出結果與上面的示例完全相同,但它們查詢的機制卻完全不同:

IQueryable<T>接口

在.NET中,IQueryable<T>繼 承於IEnumerable<T>和IQueryable接口,如下圖所示:

這裡有兩個很重要的屬性Expression和Provider,分別表示獲取與 IQueryable 的實例關聯的表達式目錄樹和獲取與此數據源關聯的查詢提供程序 ,我們所有定義在查詢表達式中方法調用或者Lambda表達式都將由該Expression 屬性表示,而最終會由Provider表示的提供程序翻譯為它所對應的數據源的查詢 語言,這個數據源可能是數據庫,XML文件或者是WebService等。該接口非常重 要,在我們自定義LINQ Provider中必須要實現這個接口。同樣對於IQueryable 的標准查詢操作都是由Queryable中的擴展方法來實現的,如下代碼所示:

public static class Queryable
{
  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, ((MethodInfo) MethodBase.GetCurrentMethod())
      .MakeGenericMethod(new Type[] { typeof(TSource) }),
      new Expression[] { source.Expression, Expression.Quote (predicate) }));
  }
  public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,
     Expression<Func<TSource, int, bool>> predicate)
   {
    if (source == null)
    {
       throw Error.ArgumentNull("source");
    }
     if (predicate == null)
    {
      throw Error.ArgumentNull("predicate");
    }
     return source.Provider.CreateQuery<TSource>(
       Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())
      .MakeGenericMethod(new Type[] { typeof(TSource) }),
      new Expression[] { source.Expression, Expression.Quote (predicate) }));
  }
}

最後還有一點,如果我們 定義的查詢需要支持Orderby等操作,還必須實現IOrderedQueryable<T> 接口,它繼承自IQueryable<T>,如下圖所示:

IQueryProvider接口

在認識了IQueryable接口之後,我們再來 看看在自定義LINQ Provider中另一個非常重要的接口IQueryProvider。它的定 義如下圖所示:

看到這裡兩組方法的參數,其實大家已經可以知道,Provider負責執 行表達式目錄樹並返回結果。如果是LINQ to SQL的Provider,則它會負責把表 達式目錄樹翻譯為T-SQL語句並並傳遞給數據庫服務器,並返回最後的執行的結 果;如果是一個Web Service的Provider,則它會負責翻譯表達式目錄樹並調用 Web Service,最終返回結果。

這裡四個方法其實就兩個操作 CreateQuery和Execute(分別有泛型和非泛型),CreateQuery方法用於構造一 個 IQueryable<T> 對象,該對象可計算指定表達式目錄樹所表示的查詢 ,返回的結果是一個可枚舉的類型,;而Execute執行指定表達式目錄樹所表示 的查詢,返回的結果是一個單一值。自定義一個最簡單的LINQ Provider,至少 需要實現IQueryable<T>和IQueryProvider兩個接口,在下篇文章中,你 將看到一個綜合的實例。

擴展LINQ的兩種方式

通過前面的講解, 我們可以想到,對於LINQ的擴展有兩種方式,一是借助於LINQ to Objects,如 果我們所做的查詢直接在.NET代碼中執行,就可以實現IEnumerable<T>接 口,而無須再去實現IQueryable並編寫自定義的LINQ Provider,如.NET中內置 的List<T>等。如我們可以編寫一段簡單自定義代碼:

public class MyData<T> : IEnumerable<T>
        where T : class
{
  public IEnumerator<T> GetEnumerator()
  {
    return null;
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return null;
  }
  // 其它成員
}

第二種擴展LINQ的方式當然就是自定義LINQ Provider了,我 們需要實現IQueryable<T>和IQueryProvider兩個接口,下面先給出一段 簡單的示意代碼,在下一篇中我們將完整的來實現一個LINQ Provider。如下代 碼所示:

public class QueryableData<TData> : IQueryable<TData>
{
  public QueryableData()
   {
    Provider = new TerryQueryProvider();
     Expression = Expression.Constant(this);
  }
  public QueryableData(TerryQueryProvider provider,
    Expression expression)
  {
    if (provider == null)
     {
      throw new ArgumentNullException ("provider");
    }
    if (expression == null)
    {
      throw new ArgumentNullException ("expression");
    }
    if (!typeof (IQueryable<TData>).IsAssignableFrom(expression.Type))
     {
      throw new ArgumentOutOfRangeException ("expression");
    }
    Provider = provider;
    Expression = expression;
  }
   public IQueryProvider Provider { get; private set; }
  public Expression Expression { get; private set; }
  public Type ElementType
  {
    get { return typeof(TData); }
  }
  public IEnumerator<TData> GetEnumerator()
  {
    return (Provider.Execute<IEnumerable<TData>> (Expression)).GetEnumerator();
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return (Provider.Execute<IEnumerable>(Expression)).GetEnumerator();
  }
}
public class TerryQueryProvider : IQueryProvider
{
  public IQueryable CreateQuery (Expression expression)
  {
    Type elementType = TypeSystem.GetElementType(expression.Type);
    try
     {
      return (IQueryable)Activator.CreateInstance(
        typeof(QueryableData<>).MakeGenericType (elementType),
        new object[] { this, expression });
    }
    catch
    {
       throw new Exception();
    }
  }
  public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
  {
    return new QueryableData<TResult>(this, expression);
  }
   public object Execute(Expression expression)
  {
     // ......
  }
  public TResult Execute<TResult> (Expression expression)
  {
    // ......
  }
}

上面這兩個接口都沒有完成,這裡只是示意性的代碼, 如果實現了這兩個接口,我們就可以像下面這樣使用了(當然這樣的使用是沒有 意義的,這裡只是為了演示):static void Main(string[] args)
{
  QueryableData<String> mydata = new QueryableData<String> {
    "TerryLee",
    "Cnblogs",
    "Dingxue"
   };
  var result = from d in mydata
         select d;
  foreach (String item in result)
  {
     Console.WriteLine(item);
  }
}

現在再來 分析一下這個執行過程,首先是實例化QueryableData<String>,同時也 會實例化TerryQueryProvider;當執行查詢表達式的時候,會調用 TerryQueryProvider中的CreateQuery方法,來構造表達式目錄樹,此時查詢並 不會被真正執行(即延遲加載),只有當我們調用GetEnumerator方法,上例中 的foreach,此時會調用TerryQueryProvider中的Execute方法,此時查詢才會被 真正執行,如下圖所示:

總結

本文介紹了在自定義LINQ Provider中兩個最重要的接口 IQueryable和IQueryProvider,希望對大家有所幫助,下一篇我我們將開發一個 完整的自定義LINQ Provider。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved