打造自己的LINQ Provider(中):IQueryable和IQueryProvider

来源:互联网 发布:模仿软件 编辑:程序博客网 时间:2024/06/06 08:50

本文首发博客园,作者TerryLee,原文地址:打造自己的LINQ Provider(中):IQueryable和IQueryProvider

概述

在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Provider。
本文为打造自己的LINQ Provider系列文章第二篇,主要详细介绍自定义LINQ Provider中两个最重要的接口IQueryable和IQueryProvider。

IEnumerable<T>接口

在上一篇《打造自己的LINQ Provider(上):Expression Tree揭秘》一文的最后,我说到了这样一句话:需要注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,带着这个问题,我们先来看下面这段代码,查询的结果query为IEnumerable<String>类型:

static void Main(string[] args){    List<String> myList = new List<String>() { "a", "ab", "cd", "bd" };    IEnumerable<String> query = from s in myList                where s.StartsWith("a")                select s;    foreach (String s in query)    {        Console.WriteLine(s);    }    Console.Read();}

这里将返回两条结果,如下图所示:

TerryLee_0170

这里就有一个问题,为什么在LINQ to Objects中返回的是IEnumerable<T>类型的数据而不是IQueryable<T>呢?答案就在本文的开始,在LINQ to Objects中查询表达式或者Lambda表达式并不翻译为表达式目录树,因为LINQ to Objects查询的都是实现了IEnmerable<T>接口的数据,所以查询表达式或者Lambda表达式都可以直接转换为.NET代码来执行,无需再经过转换为表达式目录这一步,这也是LINQ to Objects比较特殊的地方,它不需要特定的LINQ Provider。我们可以看一下IEnumerable<T>接口的实现,它里面并没有Expression和Provider这样的属性,如下图所示:

TerryLee_0171

至于LINQ to Objects中所有的标准查询操作符都是通过扩展方法来实现的,它们在抽象类Enumerable中定义,如其中的Where扩展方法如下代码所示:

public static class Enumerable{    public static IEnumerable<TSource> Where<TSource>(        this IEnumerable<TSource> source,         Func<TSource, bool> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return WhereIterator<TSource>(source, predicate);    }    public static IEnumerable<TSource> Where<TSource>(        this IEnumerable<TSource> source,         Func<TSource, int, bool> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return WhereIterator<TSource>(source, predicate);    }}

注意到这里方法的参数Func<TSource>系列委托,而非Expression<Func<TSource>>,在本文的后面,你将看到,IQueryable接口的数据,这些扩展方法的参数都是Expression<Func<TSource>>,关于它们的区别在上一篇文章我已经说过了。同样还有一点需要说明的是,在IEnumerable<T>中提供了一组扩展方法AsQueryable(),可以用来把一个IEnumerable<T>类型的数据转换为IQueryable<T>类型,如下代码所示:

static void Main(string[] args){    var myList = new List<String>()                 { "a", "ab", "cd", "bd" }.AsQueryable<String>();    IQueryable<String> query = from s in myList                where s.StartsWith("a")                select s;    foreach (String s in query)    {        Console.WriteLine(s);    }    Console.Read();} 

运行这段代码,虽然它的输出结果与上面的示例完全相同,但它们查询的机制却完全不同:

TerryLee_0170

IQueryable<T>接口

在.NET中,IQueryable<T>继承于IEnumerable<T>和IQueryable接口,如下图所示:

TerryLee_0172

这里有两个很重要的属性Expression和Provider,分别表示获取与IQueryable 的实例关联的表达式目录树和获取与此数据源关联的查询提供程序,我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,而最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言,这个数据源可能是数据库,XML文件或者是WebService等。该接口非常重要,在我们自定义LINQ Provider中必须要实现这个接口。同样对于IQueryable的标准查询操作都是由Queryable中的扩展方法来实现的,如下代码所示:

public static class Queryable{    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,             Expression<Func<TSource, bool>> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return source.Provider.CreateQuery<TSource>(            Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())            .MakeGenericMethod(new Type[] { typeof(TSource) }),             new Expression[] { source.Expression, Expression.Quote(predicate) }));    }    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,        Expression<Func<TSource, int, bool>> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return source.Provider.CreateQuery<TSource>(            Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())            .MakeGenericMethod(new Type[] { typeof(TSource) }),             new Expression[] { source.Expression, Expression.Quote(predicate) }));    }}

最后还有一点,如果我们定义的查询需要支持Orderby等操作,还必须实现IOrderedQueryable<T> 接口,它继承自IQueryable<T>,如下图所示:

TerryLee_0173 

IQueryProvider接口

在认识了IQueryable接口之后,我们再来看看在自定义LINQ Provider中另一个非常重要的接口IQueryProvider。它的定义如下图所示:

TerryLee_0174

看到这里两组方法的参数,其实大家已经可以知道,Provider负责执行表达式目录树并返回结果。如果是LINQ to SQL的Provider,则它会负责把表达式目录树翻译为T-SQL语句并并传递给数据库服务器,并返回最后的执行的结果;如果是一个Web Service的Provider,则它会负责翻译表达式目录树并调用Web Service,最终返回结果。

这里四个方法其实就两个操作CreateQuery和Execute(分别有泛型和非泛型),CreateQuery方法用于构造一个 IQueryable<T> 对象,该对象可计算指定表达式目录树所表示的查询,返回的结果是一个可枚举的类型,;而Execute执行指定表达式目录树所表示的查询,返回的结果是一个单一值。自定义一个最简单的LINQ Provider,至少需要实现IQueryable<T>和IQueryProvider两个接口,在下篇文章中,你将看到一个综合的实例。

扩展LINQ的两种方式

通过前面的讲解,我们可以想到,对于LINQ的扩展有两种方式,一是借助于LINQ to Objects,如果我们所做的查询直接在.NET代码中执行,就可以实现IEnumerable<T>接口,而无须再去实现IQueryable并编写自定义的LINQ Provider,如.NET中内置的List<T>等。如我们可以编写一段简单自定义代码:

public class MyData<T> : IEnumerable<T>                where T : class{    public IEnumerator<T> GetEnumerator()    {        return null;    }    IEnumerator IEnumerable.GetEnumerator()    {        return null;    }    // 其它成员}

第二种扩展LINQ的方式当然就是自定义LINQ Provider了,我们需要实现IQueryable<T>和IQueryProvider两个接口,下面先给出一段简单的示意代码,在下一篇中我们将完整的来实现一个LINQ Provider。如下代码所示:

public class QueryableData<TData> : IQueryable<TData>{    public QueryableData()    {        Provider = new TerryQueryProvider();        Expression = Expression.Constant(this);    }    public QueryableData(TerryQueryProvider provider,         Expression expression)    {        if (provider == null)        {            throw new ArgumentNullException("provider");        }        if (expression == null)        {            throw new ArgumentNullException("expression");        }        if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type))        {            throw new ArgumentOutOfRangeException("expression");        }        Provider = provider;        Expression = expression;    }    public IQueryProvider Provider { get; private set; }    public Expression Expression { get; private set; }    public Type ElementType    {        get { return typeof(TData); }    }    public IEnumerator<TData> GetEnumerator()    {        return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();    }    IEnumerator IEnumerable.GetEnumerator()    {        return (Provider.Execute<IEnumerable>(Expression)).GetEnumerator();    }}public class TerryQueryProvider : IQueryProvider{    public IQueryable CreateQuery(Expression expression)    {        Type elementType = TypeSystem.GetElementType(expression.Type);        try        {            return (IQueryable)Activator.CreateInstance(                typeof(QueryableData<>).MakeGenericType(elementType),                new object[] { this, expression });        }        catch        {            throw new Exception();        }    }    public IQueryable<TResult> CreateQuery<TResult>(Expression expression)    {        return new QueryableData<TResult>(this, expression);    }    public object Execute(Expression expression)    {        // ......    }    public TResult Execute<TResult>(Expression expression)    {        // ......    }}

上面这两个接口都没有完成,这里只是示意性的代码,如果实现了这两个接口,我们就可以像下面这样使用了(当然这样的使用是没有意义的,这里只是为了演示):

static void Main(string[] args){    QueryableData<String> mydata = new QueryableData<String> {         "TerryLee",        "Cnblogs",        "Dingxue"    };    var result = from d in mydata                 select d;    foreach (String item in result)    {        Console.WriteLine(item);    }}

现在再来分析一下这个执行过程,首先是实例化QueryableData<String>,同时也会实例化TerryQueryProvider;当执行查询表达式的时候,会调用TerryQueryProvider中的CreateQuery方法,来构造表达式目录树,此时查询并不会被真正执行(即延迟加载),只有当我们调用GetEnumerator方法,上例中的foreach,此时会调用TerryQueryProvider中的Execute方法,此时查询才会被真正执行,如下图所示:

TerryLee_0178 

总结

本文介绍了在自定义LINQ Provider中两个最重要的接口IQueryable和IQueryProvider,希望对大家有所帮助,下一篇我我们将开发一个完整的自定义LINQ Provider。

相关文章:打造自己的LINQ Provider(上):Expression Tree揭秘

本文首发博客园,作者TerryLee,原文地址:打造自己的LINQ Provider(中):IQueryable和IQueryProvider

原创粉丝点击