zoukankan      html  css  js  c++  java
  • 打造自己的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。

  • 相关阅读:
    redis应用场景
    java.lang.IllegalArgumentException: Result Maps collection already contains value for xxx
    Java问题解决:Java compiler level does not match the version of the installed Java project facet.
    win10 安装Oracle 11g release 2
    Oracle 11G Client客户端安装
    Oracle分页查询排序数据重复问题
    Mysql 函数使用记录(三)——UNIX_TIMESTAMP() 、UNIX_TIMESTAMP(date)
    PL/SQL Developer过期解决方法
    PL/SQL Developer登录出现——Using a filter for all users can lead to poor performance!
    Oracle Single-Row Functions(单行函数)——NULL-Related Functions
  • 原文地址:https://www.cnblogs.com/wenjl520/p/1642226.html
Copyright © 2011-2022 走看看