概述
在.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。