非常抱歉,这么长时间没写博客,没有继续写这个系列。我想解释下,因为之前一直忙于换工作,换了工作后,新公司在上班时间无法上外网,所以才有了这样的情况。经过这段时间的忙碌和适应,我想我今后还是会努力调整好时间和状态,一周应该会至少发表一篇博文,顺便说下,在这个系列完结后,我可能会写一个关于prism的系列,也希望大家能来捧场,再交代下,我之前在Hp EDS-wuhan从事.net开发,现在在光庭导航数据(武汉)有限公司从事.net开发。

     ok,情况就交代这么多,下面我们来进入正题。

     首先我们来澄清一个问题,linq直译过来是语言集成查询,顾名思义,是做查询的,像之前说的,对本地数据源有linq to object,远程的有linq to sql, linq to dataset, linq to entities, linq to xml。那么表达式树又是做什么的呢?表达式树是不是也是用来描述或者保存查询条件的呢?

     答案,否。准确地说,方法(函数)可以做什么表达式树就可以做什么。在具体阐述表达式树可以做什么,怎么做之前,我们先来提纲挈领的看看结构。之前的文章中已经提到过了IEnumerable<T>和IQueryble<T>的继承结构,所以今天我们就从我的上一篇博文中的Provider说起,至于总体的详细结构,我会在这个系列的最后一片文章中具体来说。

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

        这是我从TerryLee的博文中摘取下来的一张图,原文地址: http://www.cnblogs.com/Terrylee/archive/2008/08/25/custom-linq-provider-part-2-IQueryable-IQueryProvider.html

       从这张图中我们可以看到,IQueryable<T>继承于IQueryable,而IQueryable中有三个成员,下面我们就来具体说说这3个成员分别是做什么用的。 

       我们可以参考msdn上的资料:http://msdn.microsoft.com/zh-cn/library/system.linq.iqueryable_members.aspx

       ElementType:获取在执行与 IQueryable 的此实例关联的表达式树时返回的元素的类型。

   Expression:获取与 IQueryable 的实例关联的表达式树。

   Provider:获取与此数据源关联的查询提供程序。

       根据之前提到过的TerryLee的那篇博文:我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,而最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言,这个数据源可能是数据库,XML文件或者是WebService等。该接口非常重要,在我们自定义LINQ Provider中必须要实现这个接口。同样对于IQueryable的标准查询操作都是由Queryable中的扩展方法来实现的。

        相信大家应该还记得,我们之前提到过,IEnumerable<T>和IQueryable<T>用的是2套东西,IEnumerable<T>的扩展方法定义在Enumerable类中,而IQueryable<T>的方法定义在Queryable类中。

       下面我们再来看看Queryable类中的一个扩展方法是如何工作的,借此也看看IQueryable各成员的作用。

       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) }));
      }

         这个例子中我们看到source.Expression用来表示查询表达式,而source.Provider提供程序翻译为它所对应的数据源的查询语言,为了更直观一点,我们来看看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);
      }

         大家可以看到,在这个过程中是没有接收查询表达式和把表达式翻译成对应的数据源的查询语言这么一个过程的,它直接带入了一个谓语表达式(相当于一个匿名方法的逻辑)WhereIterator<TSource>(source, predicate);

         我们再来看看IQueryProvider这个接口,我们再来看张图

         TerryLee_0174 

          大家可以看到,这个接口里实际只有2个方法。CreateQuery和Execute(泛型和非泛型)。我来解释下这2个方法。

    按照MSDN上的资料:http://msdn.microsoft.com/zh-cn/library/bb549043.aspx

          如果给定一个表达式树,CreateQuery 方法可创建新的 IQueryable<(Of <(T>)>) 对象。返回的对象所表示的查询与特定 LINQ 提供程序相关联。

      大多数返回可枚举结果的 Queryable 标准查询运算符方法将调用此方法。这些标准查询运算符方法将向此方法传递一个表示 LINQ 查询的 MethodCallExpression

          我来说明白一点,这个方法就是把查询数据源和Expression关联起来,就是给一个查询数据源IQueryable<T>指定它的Expression是什么。

          而当我们真正foreach数据源执行查询操作时,我们调用的是Execute方法,Execute返回一个单值,Execute方法具体起到来了访问ExepressionTree,并把表达式树转换成相应的数据源可以识别的逻辑这个过程。我们可以通过一个msdn上的资料来具体看看这2个方法:http://msdn.microsoft.com/zh-cn/library/bb546158.aspx

 public class TerraServerQueryProvider : IQueryProvider
    {
        public IQueryable CreateQuery(Expression expression)
        {
            Type elementType = TypeSystem.GetElementType(expression.Type);
            try
            {
                return (IQueryable)Activator.CreateInstance(typeof(QueryableTerraServerData<>).MakeGenericType(elementType), new object[] { this, expression });
            }
            catch (System.Reflection.TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        }

        // Queryable's collection-returning standard query operators call this method.
        public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
        {
            return new QueryableTerraServerData<TResult>(this, expression);
        }

        public object Execute(Expression expression)
        {
            return TerraServerQueryContext.Execute(expression, false);
        }

        // Queryable's "single value" standard query operators call this method.
        // It is also called from QueryableTerraServerData.GetEnumerator().
        public TResult Execute<TResult>(Expression expression)
        {
            bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1");

            return (TResult)TerraServerQueryContext.Execute(expression, IsEnumerable);
        }
    }

    为了弄清楚Execute,我们再来看看TerraServerQueryContext有什么

    class TerraServerQueryContext
    {
        // Executes the expression tree that is passed to it.
        internal static object Execute(Expression expression, bool IsEnumerable)
        {
            // The expression must represent a query over the data source.
            if (!IsQueryOverDataSource(expression))
                throw new InvalidProgramException("No query over the data source was specified.");

            // Find the call to Where() and get the lambda expression predicate.
            InnermostWhereFinder whereFinder = new InnermostWhereFinder();
            MethodCallExpression whereExpression = whereFinder.GetInnermostWhere(expression);
            LambdaExpression lambdaExpression = (LambdaExpression)((UnaryExpression)(whereExpression.Arguments[1])).Operand;

            // Send the lambda expression through the partial evaluator.
            lambdaExpression = (LambdaExpression)Evaluator.PartialEval(lambdaExpression);

            // Get the place name(s) to query the Web service with.
            LocationFinder lf = new LocationFinder(lambdaExpression.Body);
            List<string> locations = lf.Locations;
            if (locations.Count == 0)
                throw new InvalidQueryException("You must specify at least one place name in your query.");

            // Call the Web service and get the results.
            Place[] places = WebServiceHelper.GetPlacesFromTerraServer(locations);

            // Copy the IEnumerable places to an IQueryable.
            IQueryable<Place> queryablePlaces = places.AsQueryable<Place>();

            // Copy the expression tree that was passed in, changing only the first
            // argument of the innermost MethodCallExpression.
            ExpressionTreeModifier treeCopier = new ExpressionTreeModifier(queryablePlaces);
            Expression newExpressionTree = treeCopier.CopyAndModify(expression);

            // This step creates an IQueryable that executes by replacing Queryable methods with Enumerable methods.
            if (IsEnumerable)
                return queryablePlaces.Provider.CreateQuery(newExpressionTree);
            else
                return queryablePlaces.Provider.Execute(newExpressionTree);
        }

        private static bool IsQueryOverDataSource(Expression expression)
        {
            // If expression represents an unqueried IQueryable data source instance,
            // expression is of type ConstantExpression, not MethodCallExpression.
            return (expression is MethodCallExpression);
        }
    }
    这是个特例正是在execute方法中执行了访问表达式树并把表达式树中的逻辑变成webservice的过程
            下面也是MSDN上的资料

          Execute 方法会执行那些返回单个值(而不是值的可枚举的序列)的查询。在枚举包含返回可枚举结果的查询的表达式树的 IQueryable<(Of <(T>)>) 对象时,会执行这些表达式树。

返回单一结果的 Queryable 标准查询运算符方法会调用 Execute。这些标准查询运算符方法将向此方法传递一个表示 LINQ 查询的 MethodCallExpression

         最后再贴一张Linq to sql 的时序图 来源:http://www.rainsts.net/article.asp?id=537

         uploads/200708/24_214509_1.png

         ok,关于provider的内容就写这么多,欢迎大家给我留言交流,下一篇我会写些轻松的内容,来写些具体的表达式树的构造和应用。           

posted on 2010-03-24 17:37  Edward.Zhan  阅读(3258)  评论(10编辑  收藏  举报