zoukankan      html  css  js  c++  java
  • 表达式树的解析.

    前言

    公司的orm框架在dapper的基础上扩展了一套表达式的方法,当时就研究了一下,把学习过程和结果记录下来,和大家分享。

    有人会说重复造轮子没必要,直接上EF。

    从我的角度来看重复造轮子的原因有以下三种:

    1、研究造轮子的原理

    2、轮子不满足现在的开发需要

    3、装B

    表达式树的作用

    最常用到的无非就是ORM的删查改的条件,ORM就是在ado.Net的基础上封装了一层表达式,最后还是将表达式解析成sql,由ado.Net去执行。

    那么我们能将表达式树解析成字符串,那么也能反过来。例如运费系统,在后台设置定义好一套计算规则。例如:对应不同的发货渠道,什么重量取哪个区间的费用,多于哪个阶段的费用还要额外费用。我们可以通过解析这套计算规则拼装好表达式树传入参数进行计算。。。

    还有别的在评论补充下。。。

    不扯多,现在我们只拿解析表达式树来学习。

    创建表达式

    首先创建4个属性的Users类

     1 namespace CG.ExpressionProject
     2 {
     3     /// <summary>
     4     /// 用户类
     5     /// </summary>
     6     public class Users
     7     {
     8         public string Name { get; set; }
     9 
    10         public int Phone { get; set; }
    11 
    12         public int Sex { get; set; }
    13 
    14         public int Age { get; set; }
    15     }
    16 }
    View Code

    接着,我们从最简单的开始,写一个二元运算表达式,F5调试监控观察。

    Expression<Func<Users, bool>> expressionUser = users => users.Name == "SkyChen"

    从上图可以看见有很多属性,在表达式主体(属性Body),我们暂时只关注三个属性,Left(左节点)、Right(右节点)和 NodeType (当前节点类型)

    简单解析

    表达式主体(users.Name == "SkyChen")是一个二元运算表达式,因此可以将Body转换成 BinaryExpression 类型来访问Left和Right。

    Left 和 Right 的 NodeType 分别为 MemberAccess(从字段或属性进行读取的运算)、Constant(常量)。

    因此可以将 Left 转换成 MemberExpression 类型来访问 Member 属性,将 Right 转换成 ConstantExpression 类型来访问 Value 属性。具体代码如下:

    public static string ResolveExpression(Expression<Func<Users, bool>> expression)
            {
                var bodyNode = (BinaryExpression)expression.Body;
    
                var leftNode = (MemberExpression)bodyNode.Left;
    
                var rightNode = (ConstantExpression)bodyNode.Right;
    
                return string.Format(" {0} {2} {1} ", leftNode.Member.Name, rightNode.Value, bodyNode.NodeType.TransferExpressionType());
            }
    View Code

    TransferExpressionType 是针对部分 ExpressionType 的一个转换。

    public static string TransferExpressionType(this ExpressionType expressionType)
            {
                string type = "";
                switch (expressionType)
                {
                    case ExpressionType.Equal:
                        type = "="; break;
                    case ExpressionType.GreaterThanOrEqual:
                        type = ">="; break;
                    case ExpressionType.LessThanOrEqual:
                        type = "<="; break;
                    case ExpressionType.NotEqual:
                        type = "!="; break;
                    case ExpressionType.AndAlso:
                        type = "And"; break;
                    case ExpressionType.OrElse:
                        type = "Or"; break;
                }
                return type;
            }
    View Code

    那么。一个最简单的表达式解析成where语句就完成了。

     

    升级

    然而,实践工作中,大家都会写相对复杂或者说多个条件的表达式。那么再采用上面的方式是无法确认表达式节点的类型进行转换的。我们可以添加一个Visit方法,根据 NodeType 转换成对应的Expression的类型,从而方法访问对应的属性进行表达式解析。

    但是,重写之前,我们得了解一件事,既然叫表达式树,意味着在子节点里,还会有多个节点,如下图:

    那么,我们假设,只要是 BinaryExpression(二元运算表达式)就会有多个子节,去访问子节点就是一个递归的过程,而终点就是 MemberExpression  和 ConstantExpression,对应字段名称和常量值的拼接。

    下面是代码实现:

    public class ExpressionTypeHelper
        {
            public StringBuilder GeWhere = new StringBuilder(100);
    
            public string Where
            {
                get { return GeWhere.ToString(); }
            }
    
            public void ResolveExpression(Expression<Func<Users, bool>> expression)
            {
                Visit(expression.Body);
            }
    
            public void Visit(Expression expression)
            {
                switch (expression.NodeType)
                {
                    case ExpressionType.Constant:
                        VisitConstantExpression(expression);
                        break;
                    case ExpressionType.MemberAccess:
                        VisitMemberExpression(expression);
                        break;
                    case ExpressionType.Convert:
                        VisitUnaryExpression(expression);
                        break;
                    default:
                        VisitBinaryExpression(expression);
                        break;
                }
            }
            public void VisitUnaryExpression(Expression expression)
            {
                var e = (UnaryExpression)expression;
                Visit(e.Operand);
            }
            public void VisitBinaryExpression(Expression expression)
            {
                var e = (BinaryExpression)expression;
                GeWhere.Append("(");
                Visit(e.Left);
    
                GeWhere.Append(e.NodeType.TransferExpressionType());
    
                Visit(e.Right);
                GeWhere.Append(")");
            }
            public void VisitConstantExpression(Expression expression)
            {
                var e = (ConstantExpression)expression;
    
                if (e.Type == typeof(string))
                {
                    GeWhere.Append("'" + e.Value + "'");
                }
                else
                {
                    GeWhere.Append(e.Value);
                }
            }
            public void VisitMemberExpression(Expression expression)
            {
                var e = (MemberExpression)expression;
                GeWhere.Append(e.Member.Name);
            }
        }
    View Code

     结果如下:

    ExpressionVisitor的使用

    一个基本的表达式解析思路基本实现了,但是!随着自己的orm的完善是不是这么多种的Expression类型都得在Visit方法添一遍,不是的。

    ExpressionVisitor类是提供给我们的表达式树解析的帮助类,我们只要定义一个类继承ExpressionVisitor,实现一个 ResolveExpression 入口方法,重写

    VisitBinary、VisitConstant、VisitMember方法,代码如下:

    public class ExpressionTrasfer : ExpressionVisitor
        {
            public StringBuilder GeWhere = new StringBuilder(100);
    
            public string Where
            {
                get { return GeWhere.ToString(); }
            }
    
            public void ResolveExpression(Expression<Func<Users, bool>> expression)
            {
                Visit(expression.Body);
            }
    
            protected override Expression VisitBinary(BinaryExpression node)
            {
                GeWhere.Append("(");
                Visit(node.Left);
    
                GeWhere.Append(node.NodeType.TransferExpressionType());
    
                Visit(node.Right);
                GeWhere.Append(")");
    
                return node;
            }
    
            protected override Expression VisitConstant(ConstantExpression node)
            {
                if (node.Type == typeof(string))
                {
                    GeWhere.Append("'" + node.Value + "'");
                }
                else if (node.Type == typeof(int))
                {
                    GeWhere.Append(node.Value);
                }
                return node;
            }
    
            protected override Expression VisitMember(MemberExpression node)
            {
                GeWhere.Append(node.Member.Name);
                return node;
            }
        }
    View Code

    结束

    一个简单的表达式解析大致完成了,当然里面还有很多可以完善,例如值类型的判断,is 还是 = ,VisitMethodCall重写等等。原理就这样,实现我这里就不一一列举。如对大家有帮助,麻烦请推荐,有不足请在下面评论提出,我会一一更改。

  • 相关阅读:
    【系列】CentOS 7.3 离线安装(无网络环境)CI CD环境之sonarqube配置
    Abp vnext 配置Swagger增加token认证
    sonarqube+gitlab runner +docker 代码质量检查问题汇总
    【EF Core】EF core中使用FluentAPI对外键进行指定配置
    【系列】CentOS 7.3 离线安装(无网络环境)CI CD环境之gitlab runner 关于私有docker仓库配置
    【系列】CentOS 7.3 离线安装(无网络环境)CI CD环境之harbor
    【系列】CentOS 7.3 离线安装(无网络环境)CI CD环境之gitlab + gitlab runner(docker in docker)
    【杂记】关于在实际项目中使用TDD的方法
    【系列】CentOS 7.3 离线安装(无网络环境)CI CD环境之docker+docker compose
    【TeamCity】使用TeamCity搭建ASP.NET Core + SVN 的 CICD环境
  • 原文地址:https://www.cnblogs.com/skychen1218/p/5730316.html
Copyright © 2011-2022 走看看