C#表达式目录树二

一个需求的探讨

我们平时封装的数据库查询方法Find<T>(int id)我们都把条件定死了,而不是动态的查询条件,不是不写,而是实现不了,写不出来,因为一个表的字段可能是Id,name等等不同的字段,而字段的值也可能是int,string,datetime等不同的数据类型,而操作符又可能是大于,小于,等于,包含这些,更有可能条件是一个条件,两个条件,三个条件等等,这些导致了我们没法写一个通用find封装,而只能根据需求写不同的方法。
上面对需求遇到的问题进行详细分析,我们真的没有办法解决上述问题吗,当然是可以的,可以用一个土办法,封装一个对象,这个对象就是一个条件的集合,包括字段,字段值,操作符这些,根据这个对象的集合就可以完整的构建一个sql语句的条件处理。这个方法不是这章我们要实现的办法,既然我们上一章介绍了表达式目录树,当然可通过表达式目录树来优雅的解决上述问题,表达式目录树既然是数据结构,我们就可以解析数据结构来按照自己的规则构造条件,又可以按照lambda的方式传入条件,这就不是我们常用的ORM框架模式吗。

表达式目录树的解析

对于表达式目录树的解析,官方是提供了ExpressionVisitor类来做解析,那么是解析什么呢,我们来看一个实例:Expression<Func<int>> expression = x => x.Aage > 5 && x.Id > 5这个表达书目录树的解析就如下图的二叉树一样,一层一才能的剖析:
在这里插入图片描述
通过上图我们能清楚的看到,我们需要把这个表达式一层一层的解析,直到最后不能解析位置,解析报错变量,常量,操作符。下面我们就是演化一下ExpressionVisitor是怎么解析的。
为了看得更清楚,我们继承ExpressionVisitor把里面的部分方法重写一下,

   /// <summary>
    /// 表达式目录树访问者扩展,遇到Add换成Subtract
    /// </summary>
    public class OperationsVisitor : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return this.Visit(expression);
        }

        /// <summary>
        /// Visit是个入口,解读node表示式
        /// 根据表达式的类型,将表达式调度到此类中更专用的访问方法之一的表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public override Expression Visit(Expression node)
        {
            Console.WriteLine($"Visit {node.ToString()}");
            return base.Visit(node);
        }
        protected override Expression VisitBinary(BinaryExpression b)
        {
            Expression left = this.Visit(b.Left);
            Expression right = this.Visit(b.Right);
            if (b.NodeType == ExpressionType.Add)
            {
                return Expression.Subtract(left, right);
            }
            else if (b.NodeType == ExpressionType.Multiply)
            {
                //Expression left = this.Visit(b.Left);//可能还是需要进一步解析的
                //Expression right = this.Visit(b.Right);
                return Expression.Divide(left, right);

                //return Expression.Divide(b.Left, b.Right);
            }
            Expression result = base.VisitBinary(b);//默认的二元访问,其实什么都不干
            return result;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            return base.VisitConstant(node);
        }
    }

我们调用:

Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
visitor.Visit(exp);

首先Visit就是入口,开始解析表达式,表达式我们最初我们都是一个二元表达式,(m * n)+ 2,左边是 m * n 右边是2,
Visit会根据表达式类型自动调的相应的方法处理,第一层是二元表达式是所以会调用VisitBinary()
在里面我们会把左右表达式再调用Visit,这个又会根据相应的表达式去调用方法,如果是二元表达式那就是调用VisitBinary,如果是常量就会调用VisitConstant来解读。所以表达式会上图看到的二叉树解析一样,会一层一层把所有的表达式解析出来。里面还有很多其他类型的解析方法,比如VisitMethodCall方法表达式,如x.Name.StartsWith("1")VisitMember成员表达式,如:x.Age

  1. ExpressionVisitor访问者类,Visit是个入口,解读表达式的入口
  2. lambda会区分参数和方法体,调度到更专业访问(处理)方法
  3. 根据表达式的类型,将表达式调度到此类中更专用的访问方法之一的表达式
  4. 默认的专业处理方法就是按照旧的模式产生一个新的
  5. 也可以自行扩展,把一些解读操作进行变化
  6. 表达式目录树是个二叉树,visit做的事儿就是一层层的解析下去,一直到最终的叶节点

通过表达式目录树构造sql

上面介绍了,可以通过解析表达式目录树把表达式的每一个节点全部解析出来,解析出来后,怎么实现构造sql条件呢,这个就需要我们自己来按照自己的语法来构造了,我们想象,我们把表达式每一个节点都解析出来,那是不是就可以根据节点来构造sql条件了。编写一个通过解析表达式目录的构造sql语句的工具类:

 public class ConditionBuilderVisitor : ExpressionVisitor
    {
    	//使用Stack是为了先进后出的顺序
        private Stack<string> _StringStack = new Stack<string>();

        public string Condition()
        {
            string condition = string.Concat(this._StringStack.ToArray());
            this._StringStack.Clear();
            return condition;
        }

        /// <summary>
        /// 二元表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node == null) throw new ArgumentNullException("BinaryExpression");

            this._StringStack.Push(")");
            base.Visit(node.Right);//解析右边
            this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
            base.Visit(node.Left);//解析左边
            this._StringStack.Push("(");

            return node;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node == null) throw new ArgumentNullException("MemberExpression");
            this._StringStack.Push(" [" + node.Member.Name + "] ");
            return node;
        }

        /// <summary>
        /// 常量表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node == null) throw new ArgumentNullException("ConstantExpression");
            this._StringStack.Push(" '" + node.Value + "' ");
            return node;
        }


        /// <summary>
        /// 方法表达式
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m == null) throw new ArgumentNullException("MethodCallExpression");

            string format;
            switch (m.Method.Name)
            {
                case "StartsWith":
                    format = "({0} LIKE {1}+'%')";
                    break;

                case "Contains":
                    format = "({0} LIKE '%'+{1}+'%')";
                    break;

                case "EndsWith":
                    format = "({0} LIKE '%'+{1})";
                    break;

                default:
                    throw new NotSupportedException(m.NodeType + " is not supported!");
            }
            this.Visit(m.Object);
            this.Visit(m.Arguments[0]);
            string right = this._StringStack.Pop();
            string left = this._StringStack.Pop();
            this._StringStack.Push(String.Format(format, left, right));

            return m;
        }
    }

调用:

 				Expression<Func<People, bool>> lambda = x => x.Age > 5
                                                            && x.Id < 5//;
                                                            && x.Id == 3
                                                            && x.Name.StartsWith("1")
                                                            && x.Name.EndsWith("1")
                                                            && x.Name.Contains("1");
                //DateTime.Parse  Format
                string sql = $"Delete From [{typeof(People).Name}] WHERE {"[Age] > 5 AND [ID] >5"}"; ;
                ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
                vistor.Visit(lambda);
                Console.WriteLine(vistor.Condition());

打印结果:

****************认识表达式目录树*************
********************MapperTest********************
********************解析表达式目录树********************
(((((( [Age]  >  '5' ) AND ( [Id]  <  '5' )) AND ( [Id]  =  '3' )) AND ( [Name]  LIKE  '1' +'%')) AND ( [Name]  LIKE '%'+ '1' )) AND ( [Name]  LIKE '%'+ '1' +'%'))

调用:

                Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == "A" || x.Id > 5;
                ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
                vistor.Visit(lambda);
                Console.WriteLine(vistor.Condition());

结果:

((( [Age]  >  '5' ) AND ( [Name]  =  'A' )) OR ( [Id]  >  '5' ))

调用:

                Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5);
                ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
                vistor.Visit(lambda);
                Console.WriteLine(vistor.Condition());

结果:

(( [Age]  >  '5' ) OR (( [Name]  =  'A' ) AND ( [Id]  >  '5' )))

调用:

                Expression<Func<People, bool>> lambda = x => (x.Age > 5 || x.Name == "A") && x.Id > 5;
                ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
                vistor.Visit(lambda);
                Console.WriteLine(vistor.Condition());

结果:

((( [Age]  >  '5' ) OR ( [Name]  =  'A' )) AND ( [Id]  >  '5' ))

至此一个简单通过表达式目录树构造sql条件的工具完成了。
因为整个过程不好通过静态的描述来讲清楚,如果想更加了解这个解析的过程,请自行调试,看看逐步追踪调试,看看这个visit是怎么递归解析出表达式的每一个节点,这样会对这个解析过程有更深刻的理解。

表达式的链接

上一章我们提到一个问题,就是根据用户输入的信息构造条件,我们使用表达式方式的方式,没有解决一个问题就是把多条件输入的条件表达式合并,通过上面的解析知识,下面我们实现这个表达式链接的扩展。

    /// <summary>
    /// 合并表达式 And Or  Not扩展
    /// </summary>
    public static class ExpressionExtend
    {
        /// <summary>
        /// 合并表达式 expr1 AND expr2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
        	//这种方式不能用,因为和导致类型不一样,比如合并两个表达式x.Age> 5 和x.Age<10,这两个表达式的x是不一样的运行会报错。
            //return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
            if (expr1 == null)
                return expr2;
            else if (expr2 == null)
                return expr1;
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.And(left, right);
            return Expression.Lambda<Func<T, bool>>(body, newParameter);
        }
        /// <summary>
        /// 合并表达式 expr1 or expr2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            if (expr1 == null)
                return expr2;
            else if (expr2 == null)
                return expr1;
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);

            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.Or(left, right);
            return Expression.Lambda<Func<T, bool>>(body, newParameter);
        }
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
        {
            if (expr == null) return null;
            var candidateExpr = expr.Parameters[0];
            var body = Expression.Not(expr.Body);

            return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
        }
    }
/// <summary>
    /// 建立新表达式
    /// </summary>
    internal class NewExpressionVisitor : ExpressionVisitor
    {
        public ParameterExpression _NewParameter { get; private set; }
        public NewExpressionVisitor(ParameterExpression param)
        {
            this._NewParameter = param;
        }
        public Expression Replace(Expression exp)
        {
            return this.Visit(exp);
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return this._NewParameter;
        }
    }

运行:

				Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
                Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
                Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2);
                Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);
                Expression<Func<People, bool>> lambda5 = lambda1.Not();
                Do1(lambda3);
                Do1(lambda4);
                Do1(lambda5);
        private static void Do1(Expression<Func<People, bool>> func)
        {
            List<People> people = new List<People>()
            {
                new People(){Id=4,Name="123",Age=4},
                new People(){Id=5,Name="234",Age=5},
                new People(){Id=6,Name="345",Age=6},
            };

            List<People> peopleList = people.Where(func.Compile()).ToList();
        }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值