专栏首页.NET技术表达式树的解析.

表达式树的解析.

前言

公司的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 }

接着,我们从最简单的开始,写一个二元运算表达式,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());
        }

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

那么。一个最简单的表达式解析成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);
        }
    }

 结果如下:

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

结束

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • .net core实践系列之短信服务-Sikiro.SMS.Api服务的实现

    上篇《.net core实践系列之短信服务-架构设计》介绍了我对短信服务的架构设计,同时针对场景解析了我的设计理念。本篇继续讲解Api服务的实现过程。

    陈珙
  • Visual Studio Package 插件开发

      这段时间公司新做了一个支付系统,里面有N个后台服务,每次有更新修改,拷贝打包发布包“不亦乐乎”。。。于是我想要不要自己定制个打包插件。

    陈珙
  • 封装自己的dapper lambda扩展-设计篇

    昨天开源了业务业余时间自己封装的dapper lambda扩展,同时写了篇博文《编写自己的dapper lambda扩展-使用篇》简单的介绍了下其使用,今天将分...

    陈珙
  • 详解 Java 对象与内存控制(上)

    不管是类变量还是实例变量,你都不能引用一个还没有定义的变量,或者在引用之前没有定义的变量,如下图所示:

    CoderJed
  • 太突然!北大方正破产了!负债3029亿元!

    日前,方正集团旗下6家上市公司齐发提示性公告表示,北京银行申请对方正集团重整。若能顺利进入重整程序,6家公司的股权结构可能会发生变化。

    昱良
  • 【CCF】打酱油

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    喜欢ctrl的cxk
  • Flutter: Semantics控件

    如果你读过有关于Flutter的代码,那么你有时候你会注意到Semantics或者SemanticsConfiguration,但官方文档却对这个很有趣的话题却...

    JarvanMo
  • 快速学习Dubbo-分布式概述

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    cwl_java
  • 一篇文章get微信开源移动端数据库组件WCDB的一切!

    微信团队已于2017年06月09日正式开源了微信自用的移动端数据库组件 WCDB(WeChat Database),详见《[资讯] 微信正式开源移动端数据库组件...

    JackJiang
  • 独家 | 一文读懂随机森林的解释和实现(附python代码)

    本文从单棵决策树讲起,然后逐步解释了随机森林的工作原理,并使用sklearn中的随机森林对某个真实数据集进行预测。

    数据派THU

扫码关注云+社区

领取腾讯云代金券