引言
一段很長很無聊的故事
2011下半年的時候開始接觸.NET同時就接觸了LINQ to SQL。好吧當時我認為LINQ to SQL就是一切(大三的C#課程老師也如此認為)。好在博客園的幾個大牛都對這個概念進行了闡述,這里可以借花獻佛。
其一,http://www.cnblogs.com/Terrylee/archive/2009/01/05/LINQ-and-LINQ-to-SQL.html
其二,http://www.cnblogs.com/JeffreyZhao/archive/2008/06/04/ajax-linq-lambda-expression.html
后來知道LINQ Provider這么一個概念,但是仍然停留在一個感性的認識上。比如如果當時問我LINQ to SQL的Provider是干嘛的,我應該說——將LINQ查詢轉換為SQL查詢,並將查詢提交給數據庫服務器執行,然后返回對應的結果(雖然現在我也能這樣回答),這是從《C#高級編程》一書中“看來”的,至於為什么印象如此深刻我也說不清楚。后來有一次想弄清楚IQueryable和IEnumerable到底是怎么回事,就去MSDN了,結果跳進一個坑里面。“演練:創建IQueryable LINQ提供程序”:
鏈接:http://msdn.microsoft.com/zh-cn/library/vstudio/bb546158.aspx
那段時間我在弄SharePoint,對“演練”這種字眼非常喜歡,所以很傻的打印了十幾頁的代碼,挨個敲。結果編譯不通過,看到如此長的代碼又無從修改,又非常沒耐心,所以這件事情就以鬧劇告終。時隔半年,我胡漢三又回來了,前段時間搞定了Exchange的管理,又臨近年末,有空閑再搗鼓自己喜歡的東西。
說明
由於博客園是個技術社區,所以我得顯得嚴謹點,這里留下幾點說明,我會在接下來的幾篇文章中(如果有的話)重復這個說明。
其一,這篇(或者系列,如果有的話)文章是為了和大家一起入門(注意不是指導)。所以所編寫的代碼僅僅是示例的,或者說是處於編寫中(完善中)的。
其二,至於為什么在學習的過程中就着手寫這些文章,那是因為我深深覺得作為入門,這些內容還是容易的,但是常常讓人卻而退步。比如在一周之前,我還問博客園中的另一位博主,請求資料。那個時候我還覺得非常困難,非常苦惱。但是,經過一些摸索,一些文章的指導之后,卻輕輕叩開了LINQ的門,一窺其瑰麗了。
其三,其實網上並不是沒有LINQ的教程(指編寫Provider)。但是“會”和不會往往隔了一點頓悟。就像“水門事件”一樣。所以作為初學者來和大家一起探討可以讓彼此更同步。
其四,這真的是一個非常有挑戰,非常有趣的內容。我接觸了之后就忍不住和大家一起分享,邀大家一起參與冒險。
最后,這里列出所有我參考的,覺得有價值的資源。
其一,MSDN的博客: http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx
這系列文章直接和本系列文章相關。07年的帖子,13年才發現,真該面壁思過。
其二,http://weblogs.asp.net/mehfuzh/archive/2007/10/04/writing-custom-linq-provider.aspx
待會會在文章中引用到這個博主寫的一個非常短小的Provider示例。
其三,博客園中某個博主的作品http://www.cnblogs.com/Terrylee/category/48778.html
大神的文章讀起來有點累,所以這系列我訪問了好幾次,愣是沒看懂怎么回事,不過里面有張圖挺不錯。
一切從接口開始
完成LINQ提供程序至少需要實現兩個接口。當朋友們看到這句話的時候會不會很希望他變成“需要實現接口XXX”呢,至少我是非常希望的。因為一旦大於一之后,事情好像就麻煩了很多很多。甚至有些時候我會覺得“接口”真是麻煩,但是轉念一下,如果沒有接口,一切似乎更加無從談起。所以很多情況下,我們並不是怕麻煩,而是缺乏指導。接口已經給了我們方向,但是這似乎還不夠到位。入門時,我們更需要一個“Hello World”,更希望盡快的成功構建一個例子,不管美、丑、長、短。藉此再次說明我寫下這些東西的初衷。理想的情況是,我能告訴大家每個接口需要實現的方法的用處(當然最好是處在的流程的哪個部分)。但是,恨遺憾,關於這點我只能盡量(好在我引用的第一篇文章中有些許說明)。從某種意義上說,這系列文章更像是讀書筆記。所以心急的朋友請直擊原作!
OK,又說了很多廢話。這兩個接口是:IQueryable,和IQueryProvider,定義分別是:
public interface IQueryable : IEnumerable { Type ElementType { get; }//返回序列元素的類型 Expression Expression { get; }//表達式目錄樹(入口) IQueryProvider Provider { get; }//提供程序 }
public interface IQueryProvider { //創建查詢 IQueryable CreateQuery(Expression expression); IQueryable<TElement> CreateQuery<TElement>(Expression expression); //執行查詢 object Execute(Expression expression); TResult Execute<TResult>(Expression expression); }
4+3=7,好吧我承認這實在是有點多,如果我們不清楚每個方法的作用的話。這里“引用”一段話,“本來只需要實現一個接口,但是后來發現分成兩個接口才能符合邏輯,但是第一個接口(IQueryable)僅僅是為了存儲一個提供程序和一個表達式目錄樹(附帶一個返回序列的元素類型)”。看到這里就應該竊喜,完全可以聲明兩個成員,分別為Expression,和Provider用來實現接口,然后在構造函數中進行賦值。至於返回值元素類型,管他怎樣,直接使用Expression的元素類型(實際上要進行更復雜的判斷,但是一般情況下OK)。這樣,一個接口就被我們干掉了。這是我的偷工減料版,鏈接中有一個相對完整版。

using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; namespace OLC.LINQ { /*C#4.0實現泛型接口的時候會自動要求實現非泛型版本*/ public class Queryable<T> : IEnumerable<T>, IQueryable<T>, IEnumerable, IQueryable, IOrderedQueryable, IOrderedQueryable<T> { /*執行這個接口的目的僅僅是為了存儲兩個內容 其一為Expression表達式目錄樹,其二為提供程序IProvider*/ private Expression expression; private IQueryProvider provider; /*構造函數*/ public Queryable(Expression expression, IQueryProvider provider) { /*注意,需要進行參數驗證,但是為了明了起見,這里先缺省*/ this.expression = expression; this.provider = provider; } /*構造函數*/ public Queryable(QueryProvider provider) { if (provider == null) { throw new ArgumentNullException("provider"); } this.provider = provider; this.expression = Expression.Constant(this); } /*返回泛型版本的枚舉器*/ public IEnumerator<T> GetEnumerator() { return ((IEnumerable<T>)Provider.Execute(expression)).GetEnumerator(); } /*非泛型版本的枚舉器*/ IEnumerator IEnumerable.GetEnumerator() { //返回執行結果 //須知,之所以IQueryable對象枚舉時返回結果,是因為...“你設置”的 return ((IEnumerable)Provider.Execute(expression)).GetEnumerator(); } /*元素類型*/ public Type ElementType { get { return typeof(T); } } /*表達式*/ public System.Linq.Expressions.Expression Expression { get { return expression; } } /*提供程序*/ public IQueryProvider Provider { get { return provider; } } /*重寫ToString方法*/ public override string ToString() { return this.provider.ToString(); } } }
當我走到這步的時候,已然輕松了很多,二變成了一,士氣大增。再來看IQueryProvider接口,4個成員要實現。還是覺得很多,但是再仔細一看,“這些可以待實現成員可以分為兩組,每組的簽名相同,只是返回值不同,一個是泛型的另一個是非泛型的。“,”非泛型的是為了留作支持動態查詢的“。這么一來,四變成了二。又為之一振。更妙的是,其中一個接口可以采用固定實現。我照着抄了一個,同樣,可以在鏈接中找到原版。

using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; namespace OLC.LINQ { /*執行IProvider接口 * 這個類型提供了一個抽象層,實現了IProvider必須實現的幾個方法中的一個, 將最后一個必要實現的Excute方法交給子類實現*/ public abstract class QueryProvider:IQueryProvider { /*主要目標之一,構造查詢*/ public IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression) { return new OLC.LINQ.Queryable<TElement>(expression, this); } /*非泛型版本*/ public IQueryable CreateQuery(System.Linq.Expressions.Expression expression) { /*嚴格的講,這里應該進行類型檢查,但是為了突出主題,先缺省*/ return (IQueryable)Activator.CreateInstance( typeof(OLC.LINQ.Queryable<>).MakeGenericType(expression.Type), new object[] { expression, this }); } /*主要目標之二,執行查詢*/ public TResult Execute<TResult>(System.Linq.Expressions.Expression expression) { return (TResult) excute(expression); } /*非泛型版本*/ public object Execute(System.Linq.Expressions.Expression expression) { return excute(expression); } /*提供抽象方法,交給下層實現*/ protected abstract object excute(Expression expression); protected abstract string getQueryText(); /*重寫ToString方法*/ public override string ToString() { return getQueryText(); } } }
現在,提供了一個抽象類,為接口中的兩個方法提供固定實現,最終我們需要實現的只剩下兩個方法(實際上是一個),這個方法名為”Excute“。當我走到這步的時候,才真正知道”將LINQ查詢轉換為目標查詢並獲取結果“的含義。也就是說,我們要在這個方法實現中做以下事情:[分析表達式目錄樹,組裝目標查詢語句,獲取結果,將結果轉換為合格的結構]
好了,既然把第一篇名字定位”接口“,意味着吊胃口的時間到了,第一篇就此結束。不知道多少朋友因為我的這篇文章拾回了”出征LINQ“的信心?原作實現了一個簡單的對SQL的提供程序,但是我不喜歡做同樣的事情,我要按照自己的嘗試對執行LDAP的搜索。誠然,網上已經有相關實現了,搜索LINQ to LDAP就能找到。但是這作為我長期跟進的一個游戲,是非常有意思的。
這里,再貼上我說過的那個短小精悍的例子。看看,如果使用一個類型來同時執行兩個接口,會是如何呢。直接上代碼。

using System; using System.Collections.Generic; using System.Text; namespace LINQ.Sample { //數據結構 public class Person { public int ID { get;set;} public string Name { get;set;} public int Age { get;set;} } }
這是用來存儲數據的(在實際的例子中,一般配合使用聲明性標簽(Attribute)進行恰當的定義,用來幫助構造目標查詢表達式)。

using System; using System.Collections.Generic; using System.Text; using System.Linq; using System.Linq.Expressions; namespace LINQ.Sample { public class PersonContext : IQueryable<Person>, IQueryProvider { #region IQueryable Members Type IQueryable.ElementType { get { return typeof(Person); } } System.Linq.Expressions.Expression IQueryable.Expression { get { return Expression.Constant(this); } } IQueryProvider IQueryable.Provider { get { return this; } } #endregion #region IEnumerable<Person> Members IEnumerator<Person> IEnumerable<Person>.GetEnumerator() { return (this as IQueryable).Provider.Execute<IEnumerator<Person>>(_expression); } private IList<Person> _person = new List<Person>(); private Expression _expression = null; #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (IEnumerator<Person>)(this as IQueryable).GetEnumerator(); } private void ProcessExpression(Expression expression) { if (expression.NodeType == ExpressionType.Equal) { ProcessEqualResult((BinaryExpression)expression); } if (expression.NodeType == ExpressionType.LessThan) { _person = GetPersons(); var query = from p in _person where p.Age < (int)GetValue((BinaryExpression)expression) select p; _person = query.ToList<Person>(); } if (expression is UnaryExpression) { UnaryExpression uExp = expression as UnaryExpression; ProcessExpression(uExp.Operand); } else if (expression is LambdaExpression) { ProcessExpression(((LambdaExpression)expression).Body); } else if (expression is ParameterExpression) { if (((ParameterExpression)expression).Type == typeof(Person)) { _person = GetPersons(); } } } private void ProcessEqualResult(BinaryExpression expression) { if (expression.Right.NodeType == ExpressionType.Constant) { string name = (String)((ConstantExpression)expression.Right).Value; ProceesItem(name); } } private void ProceesItem(string name) { IList<Person> filtered = new List<Person>(); foreach (Person person in GetPersons()) { if (string.Compare(person.Name, name, true) == 0) { filtered.Add(person); } } _person = filtered; } private object GetValue(BinaryExpression expression) { if (expression.Right.NodeType == ExpressionType.Constant) { return ((ConstantExpression)expression.Right).Value; } return null; } IList<Person> GetPersons() { return new List<Person> { new Person { ID = 1, Name="Mehfuz Hossain", Age=27}, new Person { ID = 2, Name="Json Born", Age=30}, new Person { ID = 3, Name="John Doe", Age=52} }; } #endregion #region IQueryProvider Members IQueryable<S> IQueryProvider.CreateQuery<S>(System.Linq.Expressions.Expression expression) { if (typeof(S) != typeof(Person)) throw new Exception("Only " + typeof(Person).FullName + " objects are supported."); this._expression = expression; return (IQueryable<S>) this; } IQueryable IQueryProvider.CreateQuery(System.Linq.Expressions.Expression expression) { return (IQueryable<Person>)(this as IQueryProvider).CreateQuery<Person>(expression); } TResult IQueryProvider.Execute<TResult>(System.Linq.Expressions.Expression expression) { MethodCallExpression methodcall = _expression as MethodCallExpression; foreach (var param in methodcall.Arguments) { ProcessExpression(param); } return (TResult) _person.GetEnumerator(); } object IQueryProvider.Execute(System.Linq.Expressions.Expression expression) { return (this as IQueryProvider).Execute<IEnumerator<Person>>(expression); } #endregion } }
這是兩個接口的實現。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using LINQ.Sample; namespace LINQ.Test { class Program { static void Main(string[] args) { var query = from p in new PersonContext() where p.Age < 40 select p; foreach (LINQ.Sample.Person person in query) { Console.WriteLine(person.Name); } Console.ReadLine(); } } }
這是調用示例。當然可以修改查詢語句,但是注意,作為一個示例,作者並沒有實現全部運算符,而且數據源是來自內存的。
嗯,貼圖是美德。另外,中文社區里真的很少見討論這方面的帖子,高手就當是看一個小孩子在玩耍吧。:)