從接口說起——自定義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();
        }

    }
}

這是調用示例。當然可以修改查詢語句,但是注意,作為一個示例,作者並沒有實現全部運算符,而且數據源是來自內存的。

 

 嗯,貼圖是美德。另外,中文社區里真的很少見討論這方面的帖子,高手就當是看一個小孩子在玩耍吧。:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM