• 不可不知的表达式树(3)定制IQueryProvider


    前面我们说到利用表达式树技术实现LINQ-to-SQL,实际上可以针对任何数据源,实现LINQ-to-Everything。这里还涉及到两个重要的接口即IQueryable和IQueryProvider,这些一起为实现通过LINQ访问各种数据源提供了统一的编程接口。

    一、认识IQueryable<T>

    接口源码如下:

     1 namespace System.Linq
     2 {
     3     //提供对未指定数据类型的特定数据源的查询进行计算的功能。
     4     public interface IQueryable : IEnumerable
     5     {
     6         //获取与 System.Linq.IQueryable 的实例关联的表达式树。
     7         Expression Expression { get; }
     8         //获取在执行与 System.Linq.IQueryable 的此实例关联的表达式树时返回的元素的类型。
     9         Type ElementType { get; }
    10         //获取与此数据源关联的查询提供程序。
    11         IQueryProvider Provider { get; }
    12     }
    13     public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
    14     {
    15     }
    16 }

     IQueryable中定义了三个只读的属性,ElementType即为查询对象的类型,Expression即为表达式树,我们的Linq查询表达式都将转换为表达式树,而Provider表示数据源查询提供程序,将Expression翻译为数据源的查询语言,如Sql,并负责最终的数据查询实现。所以我们必须要实现IQueryProvider接口。

    二、IQueryProvider

    接口源码如下:

     1 namespace System.Linq
     2 {
     3     public interface IQueryProvider
     4     {
     5         IQueryable CreateQuery(Expression expression);
     6         IQueryable<TElement> CreateQuery<TElement>(Expression expression);
     7         object Execute(Expression expression);
     8         TResult Execute<TResult>(Expression expression);
     9     }
    10 }

    IQueryProvider接口包含两组方法,CreateQuery顾名思义即是由Provider通过传入的Expression参数创建并返回一个新的IQueryable查询实例。

    Execute则是真正解析Expression的,解析即遍历并翻译其为特定查询语言的过程,然后进行查询,并返回查询结果object。

    三、定制IQueryProvider

    定制查询我们自己的数据源,比如最终实现如下的代码调用方式,该如何实现呢?

     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             var provider = new MyQueryProvider();
     6             var queryable = new Query<Person>(provider);
     7 
     8             var query =
     9                 from p in queryable
    10                 where p.Age >= 18 &&
    11                 p.Gender== “男” 
    12                 select p;
    13 
    14             var list = query.ToList();
    15             Console.ReadLine();
    16         }
    17     }

    下面先给出这段代码执行的时序图来帮助我们进一步理解整个执行过程:

    代码5-6行,对应步骤1-4,实例化了我们自定义的IQueryable;

    代码8-12行,对应步骤5-8,在IQueryable实例基础上,进行Linq查询,查询表达式翻译为Expression,构造出新的IQueryable实例;

    代码14行,对应步骤9-12,ToList()方法调用,延迟加载执行,查询出结果并返回。

    从中可以看出我们的类图大概是这样的:

    大体框架就是如此,然后我们需要做的最重要的事情就是实现MyQueryProvider的Execute(),在这个核心方法里解析Expression,翻译为特定数据源的查询语句,并进行查询。

    实例代码:

    1   public override object Execute(Expression expression)
    2   {
    3      //遍历表达式树,生成特定数据源的查询语句,例如sql
    4      String myLang = new MyExpressionVisitor().ProcessExpression(expression);
    5      //根据查询语句进行查询得到结果
    6      IEnumerable<Person> results = PersonHelper.DoQuery(myLang);
    7      return results;
    8   }

    至于第6行代码,根据查询语句查询出结果,就是各种Helper的职责了,例如SqlHelper。

    然后最重要的,还是ProcessExpression(expression)的实现了,换句话说归根结底这个IQueryProvider最核心的功能就是遍历表达式树。

    最后给出主要的遍历代码:

      1     public class MyExpressionVisitor
      2     {
      3         private string _myLang;
      4         // 入口方法
      5         public string ProcessExpression(Expression expression)
      6         {
      7             _myLang = string.Empty;
      8             VisitExpression(expression);
      9             return _myLang;
     10         }
     11         private void VisitExpression(Expression expression)
     12         {
     13             switch (expression.NodeType)
     14             {
     15                 // 访问 &&
     16                 case ExpressionType.AndAlso:
     17                     VisitAndAlso((BinaryExpression)expression);
     18                     break;
     19                 // 访问 等于
     20                 case ExpressionType.Equal:
     21                     VisitEqual((BinaryExpression)expression);
     22                     break;
     23                 // 访问 小于和小于等于
     24                 case ExpressionType.LessThan:
     25                 case ExpressionType.LessThanOrEqual:
     26                     VisitLessThanOrEqual((BinaryExpression)expression);
     27                     break;
     28                 // 访问大于和大于等于
     29                 case ExpressionType.GreaterThan:
     30                 case ExpressionType.GreaterThanOrEqual:
     31                     GreaterThanOrEqual((BinaryExpression)expression);
     32                     break;
     33                 // 访问调用方法,主要用于解析Contains方法
     34                 case ExpressionType.Call:
     35                     VisitMethodCall((MethodCallExpression)expression);
     36                     break;
     37                 // 访问Lambda表达式
     38                 case ExpressionType.Lambda:
     39                     VisitExpression(((LambdaExpression)expression).Body);
     40                     break;
     41             }
     42         }
     43 
     44         // 访问  &&
     45         private void VisitAndAlso(BinaryExpression andAlso)
     46         {
     47             VisitExpression(andAlso.Left);
     48             VisitExpression(andAlso.Right);
     49         }
     50 
     51         // 访问 等于
     52         private void VisitEqual(BinaryExpression expression)
     53         {
     54             //...
     55         }
     56 
     57         // 访问大于等于
     58         private void GreaterThanOrEqual(BinaryExpression expression)
     59         {
     60             //...
     61         }
     62 
     63         // 访问 小于和小于等于
     64         private void VisitLessThanOrEqual(BinaryExpression expression)
     65         {
     66             //...
     67         }
     68 
     69         // 访问 方法调用
     70         private void VisitMethodCall(MethodCallExpression expression)
     71         {
     72             //...
     73         }
     74 
     75         // 获取属性值
     76         private Object GetMemberValue(MemberExpression memberExpression)
     77         {
     78             MemberInfo memberInfo;
     79             Object obj;
     80 
     81             if (memberExpression == null)
     82                 throw new ArgumentNullException("memberExpression");
     83 
     84 
     85             if (memberExpression.Expression is ConstantExpression)
     86                 obj = ((ConstantExpression)memberExpression.Expression).Value;
     87             else if (memberExpression.Expression is MemberExpression)
     88                 obj = GetMemberValue((MemberExpression)memberExpression.Expression);
     89             else
     90                 throw new NotSupportedException("Expression type not supported: "
     91                     + memberExpression.Expression.GetType().FullName);
     92 
     93             memberInfo = memberExpression.Member;
     94             if (memberInfo is PropertyInfo)
     95             {
     96                 PropertyInfo property = (PropertyInfo)memberInfo;
     97                 return property.GetValue(obj, null);
     98             }
     99             else if (memberInfo is FieldInfo)
    100             {
    101                 FieldInfo field = (FieldInfo)memberInfo;
    102                 return field.GetValue(obj);
    103             }
    104             else
    105             {
    106                 throw new NotSupportedException("MemberInfo type not supported: "
    107                     + memberInfo.GetType().FullName);
    108             }
    109         }
    110     }

    至此,表达式树系列文章完结,希望对大家有所帮助。

    文章参考: The Wayward WebLog.

  • 相关阅读:
    TortoiseGit状态图标不能正常显示的解决办法
    git安装及命令使用和github网站
    git commit 代码时提示: Warning: Your console font probably doesn‘t support Unicode.
    msysgit之git bash方便的复制粘贴,默认工作目录,窗口大小,提高速度等小窍门
    Apache与Tomcat的整合
    Android UI效果实现——Activity滑动退出效果
    多平台Native库打入JAR包发布实战
    WPF使用Win32进行GDI截屏笔记
    使用Gradle将Maven库打包成Jar
    【转载/修改】ScrollLayout代码修正,追加模仿viewpager滚动速度
  • 原文地址:https://www.cnblogs.com/jiujiduilie/p/8987606.html
Copyright © 2020-2023  润新知