引言
一段很长很无聊的故事
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(); } } }
这是调用示例。当然可以修改查询语句,但是注意,作为一个示例,作者并没有实现全部运算符,而且数据源是来自内存的。
嗯,贴图是美德。另外,中文社区里真的很少见讨论这方面的帖子,高手就当是看一个小孩子在玩耍吧。:)