zoukankan      html  css  js  c++  java
  • 从接口说起——自定义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();
            }
    
        }
    }

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

     

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

  • 相关阅读:
    Qt5.9/C++项目开发架构理论
    Qt5及模块架构分析
    简单工厂模式实例
    SQL Server 存储过程通用分页
    面试问题
    ASP.Net 基础知识
    财务自由之路名句
    javascript 根据输入的关键词自动提示
    .NET批量更新
    在windows7上配置xampp虚拟主机
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/2875835.html
Copyright © 2011-2022 走看看