打造自己的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。


















本文转自lihuijun51CTO博客,原文链接: http://blog.51cto.com/terrylee/94939 ,如需转载请自行联系原作者

相关文章
|
3月前
|
SQL 开发框架 .NET
聊聊 System.Linq.Dynamic,以及分享一个使用 System.Linq.Dynamic 扩展 LINQ 查询的详细例子
聊聊 System.Linq.Dynamic,以及分享一个使用 System.Linq.Dynamic 扩展 LINQ 查询的详细例子
114 0
|
.NET 开发框架 数据库
深入调研Linq to Objects Join Linq to Entity
最近工作中遇到数据库组合查询带来的一些问题,因此有必要调研一下Linq to Objects Join Linq to Entity。参考一些网友的代码案例,深入实践了一下使用EntityFramework Code First 下的组合查询。
1250 0
|
SQL .NET 数据库
IEnumerable和IQueryable在使用时的区别
最近在调研数据库查询时因使用IEnumerable进行Linq to entity的操作,造成数据库访问缓慢。此文讲述的便是IEnumerable和IQueryable的区别。 微软对IEnumerable的定义使用场景为Linq to Object,也就是涉及到内存操作时使用。
1310 0