从接口说起——自定义LINQ Provider实现LINQ to LDAP查询(其一)

引言

一段很长很无聊的故事

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)。这样,一个接口就被我们干掉了。这是我的偷工减料版,链接中有一个相对完整版。

View Code
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个成员要实现。还是觉得很多,但是再仔细一看,“这些可以待实现成员可以分为两组,每组的签名相同,只是返回值不同,一个是泛型的另一个是非泛型的。“,”非泛型的是为了留作支持动态查询的“。这么一来,四变成了二。又为之一振。更妙的是,其中一个接口可以采用固定实现。我照着抄了一个,同样,可以在链接中找到原版。

View Code
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就能找到。但是这作为我长期跟进的一个游戏,是非常有意思的。

这里,再贴上我说过的那个短小精悍的例子。看看,如果使用一个类型来同时执行两个接口,会是如何呢。直接上代码。

View Code
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)进行恰当的定义,用来帮助构造目标查询表达式)。

View Code
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
    }
}

这是两个接口的实现。

View Code
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();
        }

    }
}

这是调用示例。当然可以修改查询语句,但是注意,作为一个示例,作者并没有实现全部运算符,而且数据源是来自内存的。

 

 嗯,贴图是美德。另外,中文社区里真的很少见讨论这方面的帖子,高手就当是看一个小孩子在玩耍吧。:)

posted @ 2013-01-24 23:07  LibraJM  阅读(2439)  评论(10编辑  收藏  举报