打造自己的LINQ Provider(中):IQueryable和IQueryProvider概述在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其強大而優(yōu)雅的編程方式贏得了開發(fā)人員的喜愛,而各種LINQ Provider更是滿天飛,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趨勢。LINQ本身也提供了很好的擴展性,使得我們可以輕松的編寫屬于自己的LINQ Provider。 IEnumerable<T>接口在上一篇《打造自己的LINQ Provider(上):Expression Tree揭秘》一文的最后,我說到了這樣一句話:需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因為它并不翻譯為表達式目錄樹,帶著這個問題,我們先來看下面這段代碼,查詢的結(jié)果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(); } 這里將返回兩條結(jié)果,如下圖所示:
這里就有一個問題,為什么在LINQ to Objects中返回的是IEnumerable<T>類型的數(shù)據(jù)而不是IQueryable<T>呢?答案就在本文的開始,在LINQ to Objects中查詢表達式或者Lambda表達式并不翻譯為表達式目錄樹,因為LINQ to Objects查詢的都是實現(xiàn)了IEnmerable<T>接口的數(shù)據(jù),所以查詢表達式或者Lambda表達式都可以直接轉(zhuǎn)換為.NET代碼來執(zhí)行,無需再經(jīng)過轉(zhuǎn)換為表達式目錄這一步,這也是LINQ to Objects比較特殊的地方,它不需要特定的LINQ Provider。我們可以看一下IEnumerable<T>接口的實現(xiàn),它里面并沒有Expression和Provider這樣的屬性,如下圖所示:
至于LINQ to Objects中所有的標準查詢操作符都是通過擴展方法來實現(xiàn)的,它們在抽象類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); } } 注意到這里方法的參數(shù)Func<TSource>系列委托,而非Expression<Func<TSource>>,在本文的后面,你將看到,IQueryable接口的數(shù)據(jù),這些擴展方法的參數(shù)都是Expression<Func<TSource>>,關(guān)于它們的區(qū)別在上一篇文章我已經(jīng)說過了。同樣還有一點需要說明的是,在IEnumerable<T>中提供了一組擴展方法AsQueryable(),可以用來把一個IEnumerable<T>類型的數(shù)據(jù)轉(zhuǎn)換為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(); } 運行這段代碼,雖然它的輸出結(jié)果與上面的示例完全相同,但它們查詢的機制卻完全不同: IQueryable<T>接口在.NET中,IQueryable<T>繼承于IEnumerable<T>和IQueryable接口,如下圖所示:
這里有兩個很重要的屬性Expression和Provider,分別表示獲取與IQueryable 的實例關(guān)聯(lián)的表達式目錄樹和獲取與此數(shù)據(jù)源關(guān)聯(lián)的查詢提供程序,我們所有定義在查詢表達式中方法調(diào)用或者Lambda表達式都將由該Expression屬性表示,而最終會由Provider表示的提供程序翻譯為它所對應(yīng)的數(shù)據(jù)源的查詢語言,這個數(shù)據(jù)源可能是數(shù)據(jù)庫,XML文件或者是WebService等。該接口非常重要,在我們自定義LINQ Provider中必須要實現(xiàn)這個接口。同樣對于IQueryable的標準查詢操作都是由Queryable中的擴展方法來實現(xiàn)的,如下代碼所示: 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等操作,還必須實現(xiàn)IOrderedQueryable<T> 接口,它繼承自IQueryable<T>,如下圖所示:
IQueryProvider接口在認識了IQueryable接口之后,我們再來看看在自定義LINQ Provider中另一個非常重要的接口IQueryProvider。它的定義如下圖所示:
看到這里兩組方法的參數(shù),其實大家已經(jīng)可以知道,Provider負責執(zhí)行表達式目錄樹并返回結(jié)果。如果是LINQ to SQL的Provider,則它會負責把表達式目錄樹翻譯為T-SQL語句并并傳遞給數(shù)據(jù)庫服務(wù)器,并返回最后的執(zhí)行的結(jié)果;如果是一個Web Service的Provider,則它會負責翻譯表達式目錄樹并調(diào)用Web Service,最終返回結(jié)果。 這里四個方法其實就兩個操作CreateQuery和Execute(分別有泛型和非泛型),CreateQuery方法用于構(gòu)造一個 IQueryable<T> 對象,該對象可計算指定表達式目錄樹所表示的查詢,返回的結(jié)果是一個可枚舉的類型,;而Execute執(zhí)行指定表達式目錄樹所表示的查詢,返回的結(jié)果是一個單一值。自定義一個最簡單的LINQ Provider,至少需要實現(xiàn)IQueryable<T>和IQueryProvider兩個接口,在下篇文章中,你將看到一個綜合的實例。 擴展LINQ的兩種方式通過前面的講解,我們可以想到,對于LINQ的擴展有兩種方式,一是借助于LINQ to Objects,如果我們所做的查詢直接在.NET代碼中執(zhí)行,就可以實現(xiàn)IEnumerable<T>接口,而無須再去實現(xiàn)IQueryable并編寫自定義的LINQ Provider,如.NET中內(nèi)置的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了,我們需要實現(xiàn)IQueryable<T>和IQueryProvider兩個接口,下面先給出一段簡單的示意代碼,在下一篇中我們將完整的來實現(xiàn)一個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) { // ...... } } 上面這兩個接口都沒有完成,這里只是示意性的代碼,如果實現(xiàn)了這兩個接口,我們就可以像下面這樣使用了(當然這樣的使用是沒有意義的,這里只是為了演示): 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); } } 現(xiàn)在再來分析一下這個執(zhí)行過程,首先是實例化QueryableData<String>,同時也會實例化TerryQueryProvider;當執(zhí)行查詢表達式的時候,會調(diào)用TerryQueryProvider中的CreateQuery方法,來構(gòu)造表達式目錄樹,此時查詢并不會被真正執(zhí)行(即延遲加載),只有當我們調(diào)用GetEnumerator方法,上例中的foreach,此時會調(diào)用TerryQueryProvider中的Execute方法,此時查詢才會被真正執(zhí)行,如下圖所示:
總結(jié)本文介紹了在自定義LINQ Provider中兩個最重要的接口IQueryable和IQueryProvider,希望對大家有所幫助,下一篇我我們將開發(fā)一個完整的自定義LINQ Provider。 相關(guān)文章:打造自己的LINQ Provider(上):Expression Tree揭秘 作者:TerryLee
出處:http://terrylee.cnblogs.com 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權(quán)利。 分類: [06] LINQ之美 |
|
來自: Wiley Library > 《C#》