• 【手撸一个ORM】第五步、Expression(表达式目录树)转换为Where子句


    说明

    在SQL中,查询、修改比较常用到WHERE子句,在这里根据使用场景不同,定义了两个类,一个用于查询,一个用于修改(插入)操作。原因是:

    • 查询操作支持一级导航属性查询,如student.School.Name="xxx",在这里使用LEFT JOIN 的方式实现,所以拼接的时候需要考虑子表别名(两个表有相同字段,所以必须通过表名前缀进行区分)等问题。
    • 更新操作不支持导航属性,如 student.School.Name="xxx" 将会被忽略。说明一点:这里也有简单的方法可以实现一级导航属性的查询,方法是将其解释为子查询 如 Update Student Set xxx WHERE SchoolId IN (SELECT Id FROM School WHERE Name='xxx')。这个扩展应该比较简单,请自行实现。

     一些思考:

    需要实现参数查询,这是基本要求,虽然使用其他方式也能屏蔽SQL注入,但参数化无疑是更好的选择。

    下面是尝试过的实现方式:

    • 继承ExpressionVisitor,在遍历节点时拼接查询条件。如果表达式中完全是二元操作,如 s => s.Name=="张三" && s.IsDel=false,这种方法可以实现的很好,但是如果有如下情况 s => !s.IsDel,那问题就来了,我们需要的是 [Student].[IsDel]=0,而这种方式只能解析出 [Student].[IsDel],类似的情形还有不少,所以只能放弃。
    • 继承ExpressionVisitor这种方式尝试失败,得到的教训是必须将二元表达式中 AndAlso、OrElse与其他表达式区分出来,将AND和OR区间的子句摘出来解析后再用AND | OR连接起来,这个也简单,遍历AND | OR,将子句封装起来保存到列表,大概是这样的:new ClauseHolder { Type="And", Expr = 表达式 },最后再将封装好的列表解释成SQL语句,但这种方式也有缺陷,就是当表达式树有嵌套的情况,简单的List<ClauseHolder>无法体现,如果非要添加嵌套,逻辑比较复杂,所以最后也放弃了。
    • 既然上面两种解决方式都有缺陷,那就尝试将他们结合起来,最终的方案是将And|Or的子句摘出来单独解析(第二种方案),将解析过程中直接拼装字符串(第一种方案)。

    思路分析

    将单一条件作为一个子句 如SQL语句时这样的:Student.Id>0 AND Student.IsDel=0,其中 Student.Id > 0 是一个子句, Student.IsDel = 0 是一个子句。

    整体的思路就是,从右到左,将子句转换为SQL语句,并使用 AND | OR 连接起来。我们知道,表达式目录树是二叉树的结构,从右到左一级级进入,对AND|Or左侧表达式的解析要使用递归操作。

    分析到这里,我们似乎找到了一点点头绪,抛开其他,单一子句的解析先整出来,下面是我想到的几种情形:

    表达式目录树解析的最大难点就是可能出现的情况太多,每种情况对应的节点类型、操作方式可能都不一样。我暂时能想到的就这么多,解决的思路也在上面。其中用的最多的,就是判断根节点类型和节点取值操作,还好,在上一篇Expression扩展中我们已经封装了响应的方法,直接调用即可。

    当然,在实际代码中,我们还加入了一些逻辑,如导航属性的处理,请看下面的表达式

    Expression<Func<Student, bool>> expr1 = s => s.School.IsDel;

    这里判断是的Student类的导航属性School的IsDel属性,我们的查询时支持这种只有一级的导航属性的,所以对这种情况进行了一些处理。另外可能数据表列名与属性名并不相同,在拼接的时候,不得不从实体缓存中找到相应的属性定义,将其转换为对应的数据列。

    接下来,需要应对的将组合条件拼接起来的问题,并不复杂,这里就不再多做解释,直接看代码就可以了。

    条件表达式解释器基类 [BaseConditionResolver.cs]

    using Dapper;
    using System.Collections.Generic;
    using MyOrm.DbParameters;
    using MyOrm.Reflections;
    
    namespace MyOrm.Expressions
    {
        public class QueryConditionResolveResult
        {
            public string Condition { get; set; }
    
            public MyDbParameters Parameters { get; set; } = new MyDbParameters();
    
            public List<MyEntity> NavPropertyList { get; set; } = new List<MyEntity>();
        }
    
        public abstract class BaseConditionResolver<T>
        {
            #region 字段
    
            protected MyEntity Entity { get; }
    
            private readonly string _prefix;
    
            private readonly Stack<string> _stack = new Stack<string>();
    
            protected readonly QueryConditionResolveResult Result = new QueryConditionResolveResult();
    
            private int _parameterIndex;
    
            #endregion
    
            #region 构造函数
    
            protected BaseConditionResolver(string prefix = "@")
            {
                Entity = MyEntityContainer.Get(typeof(T));
                _prefix = prefix;
            }
    
            protected BaseConditionResolver(MyEntity entity, string prefix = "@")
            {
                Entity = entity;
                _prefix = prefix;
            }
    
            #endregion
    
            #region 返回结果
    
            public QueryConditionResolveResult Resolve(Expression expression)
            {
                Visit(expression);
                var condition = string.Concat(_stack.ToArray());
                Result.Condition = condition;
                _stack.Clear();
                return Result;
            }
            #endregion
    
            #region 处理表达式目录树
    
            private void Visit(Expression node)
            {
                if (node.NodeType == ExpressionType.AndAlso ||
                    node.NodeType == ExpressionType.OrElse)
                {
                    var expression = (BinaryExpression)node;
                    var right = expression.Right;
                    var left = expression.Left;
    
                    var rightString = ResolveExpression(right);
                    var op = node.NodeType.ToSqlOperator();
    
                    _stack.Push(")");
                    _stack.Push(rightString);
                    _stack.Push(op);
                    Visit(left);
                    _stack.Push("(");
                }
                else
                {
                    _stack.Push(ResolveExpression(node));
                }
            }
    
            #endregion
    
            #region 解析表达式
    
            private string ResolveExpression(Expression node, bool isClause = true)
            {
                switch (node.NodeType)
                {
                    case ExpressionType.AndAlso:
                    case ExpressionType.OrElse:
                        {
                            var expression = (BinaryExpression)node;
                            var right = ResolveExpression(expression.Right, false);
                            var op = node.NodeType.ToSqlOperator();
                            var left = ResolveExpression(expression.Left, false);
    
                            return $"({left} {op} {right})";
                        }
                    case ExpressionType.MemberAccess:
                        {
                            var expression = (MemberExpression)node;
                            var rootType = expression.GetRootType(out var stack);
    
                            // 如果是参数表达式
                            if (rootType == ExpressionType.Parameter)
                            {
                                // 如果独立的语句,如 s.IsActive ,则返回 [列名]=1;
                                return isClause ? $"{ResolveStackToField(stack)}=1" : $"{ResolveStackToField(stack)}";
                            }
    
                            // 如果不是参数表达式,则计算表达式的值(可能是本地变量、常数等)
                            var val = node.GetValue();
                            if (isClause)   // var isActive=true; s => isActive
                            {
                                if (val is bool b)
                                {
                                    return b ? "1=1" : "1=0";
                                }
                            }
                            var parameterName = GetParameterName();
                            Result.Parameters.Add(parameterName, val);
                            return parameterName;
                        }
                    case ExpressionType.Call:
                        {
                            // 方法调用
                            var expression = (MethodCallExpression)node;
                            var method = expression.Method.Name;
    
                            if (expression.Object != null &&
                                expression.Object.NodeType == ExpressionType.MemberAccess)
                            {
                                var rootType = ((MemberExpression)expression.Object).GetRootType(out var stack);
                                if (rootType == ExpressionType.Parameter)
                                {
                                    var value = expression.Arguments[0].GetValue();
                                    switch (method)
                                    {
                                        case "Contains":
                                            value = $"%{value}%";
                                            break;
                                        case "StartsWith":
                                            value = $"{value}%";
                                            break;
                                        case "EndsWith":
                                            value = $"%{value}";
                                            break;
                                    }
    
                                    var parameterName = GetParameterName();
                                    Result.Parameters.Add(parameterName, value);
                                    return $"{ResolveStackToField(stack)} LIKE {parameterName}";
                                }
                                else
                                {
                                    var value = node.GetValue();
                                    if (isClause)
                                    {
                                        if (value is bool b)
                                        {
                                            return b ? "1=1" : "1=0";
                                        }
                                    }
                                    var parameterName = GetParameterName();
                                    Result.Parameters.Add(parameterName, value);
                                    return $"{parameterName}";
                                }
                            }
                            else
                            {
                                var value = node.GetValue();
                                if (isClause)
                                {
                                    if (value is bool b)
                                    {
                                        return b ? "1=1" : "1=0";
                                    }
                                }
                                var parameterName = GetParameterName();
                                Result.Parameters.Add(parameterName, value);
                                return $"{parameterName}";
                            }
                        }
                    case ExpressionType.Not:
                        {
                            var expression = ((UnaryExpression)node).Operand;
                            if (expression.NodeType == ExpressionType.MemberAccess)
                            {
                                var rootType = ((MemberExpression)expression).GetRootType(out var stack);
                                if (rootType == ExpressionType.Parameter)
                                {
                                    return $"{ResolveStackToField(stack)}=0";
                                }
                            }
    
                            break;
                        }
                    // 常量、本地变量
                    case ExpressionType.Constant when !isClause:
                        {
                            var val = node.GetValue();
                            var parameterName = GetParameterName();
                            Result.Parameters.Add(parameterName, val);
                            return parameterName;
                        }
                    case ExpressionType.Constant:
                        {
                            var expression = (ConstantExpression)node;
                            var value = expression.Value;
                            return value is bool b ? b ? "1=1" : "1=0" : string.Empty;
                        }
                    case ExpressionType.Equal:
                    case ExpressionType.NotEqual:
                    case ExpressionType.GreaterThan:
                    case ExpressionType.GreaterThanOrEqual:
                    case ExpressionType.LessThan:
                    case ExpressionType.LessThanOrEqual:
                        {
                            // 二元操作符,等于、不等于、大于、小于等
                            var expression = (BinaryExpression)node;
                            var right = expression.Right;
                            var left = expression.Left;
                            var op = expression.NodeType.ToSqlOperator();
    
                            if (op == "=" || op == "<>")
                            {
                                if (right.NodeType == ExpressionType.Constant && right.GetValue() == null)
                                {
                                    return op == "="
                                        ? $"{ResolveExpression(left, false)} IS NULL"
                                        : $"{ResolveExpression(left, false)} IS NOT NULL";
                                }
                            }
    
                            return $"{ResolveExpression(left, false)} {op} {ResolveExpression(right, false)}";
                        }
                    default:
                        {
                            var value = node.GetValue();
                            if (isClause)
                            {
                                return value is bool b ? b ? "1=1" : "1=0" : string.Empty;
                            }
    
                            var parameterName = GetParameterName();
                            Result.Parameters.Add(parameterName, value);
                            return parameterName;
                        }
                }
    
                return string.Empty;
            }
    
            #endregion
    
            #region 辅助方法
    
            protected abstract string ResolveStackToField(Stack<string> parameterStack);
    
            private string GetParameterName()
            {
                return $"{_prefix}__p_{_parameterIndex++}";
            }
    
            #endregion
        }
    }

    查询条件表达式解释器 [QueryConditionResolver.cs]

    using MyOrm.Reflections;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace MyOrm.Expressions
    {
        public class QueryConditionResolver<T> : BaseConditionResolver<T>
        {
            public QueryConditionResolver(string prefix = "@") : base(prefix)
            { }
    
            public QueryConditionResolver(MyEntity entity, string prefix = "@") : base(entity, prefix)
            { }
    
            protected override string ResolveStackToField(Stack<string> parameterStack)
            {
                switch (parameterStack.Count)
                {
                    case 2:
                    {
                        // 调用了导航属性
                        var propertyName = parameterStack.Pop();
                        var propertyFieldName = parameterStack.Pop();
    
                        MyEntity propertyEntity = Result.NavPropertyList.SingleOrDefault(p => p.Name == propertyName);
                        if(propertyEntity == null)
                        {
                            var prop = Entity.Properties.Single(p => p.Name == propertyName);
                            propertyEntity = MyEntityContainer.Get(prop.PropertyInfo.PropertyType);
                            Result.NavPropertyList.Add(propertyEntity);
                        }
                        
                        var propertyProperty = propertyEntity.Properties.Single(p => p.Name == propertyFieldName);
                        return $"[{propertyName}].[{propertyProperty.FieldName}]";
                    }
                    case 1:
                    {
                        var propertyName = parameterStack.Pop();
                        var propInfo = Entity.Properties.Single(p => p.Name == propertyName);
                        return $"[{Entity.TableName}].[{propInfo.FieldName}]";
                    }
                    default:
                        throw new ArgumentException("尚未支持大于2层属性调用。如 student.Clazz.School.Id>10,请使用类似 student.Clazz.SchoolId > 0 替代");
                }
            }
        }
    }

    更新条件表达式解释器 [EditConditionResolver.cs]

    using MyOrm.Reflections;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace MyOrm.Expressions
    {
        public class EditConditionResolver<T> : BaseConditionResolver<T>
        {
            public EditConditionResolver(string prefix = "@") : base(prefix)
            { }
    
            public EditConditionResolver(MyEntity entity, string prefix = "@") : base(entity, prefix)
            { }
    
            protected override string ResolveStackToField(Stack<string> parameterStack)
            {
                if (parameterStack.Count != 1)
                    throw new ArgumentException(
                        "不支持大于1层属性调用");
    
                var propertyName = parameterStack.Pop();
                var propInfo = Entity.Properties.Single(p => p.Name == propertyName);
                return $"[{Entity.TableName}].[{propInfo.FieldName}]";
            }
        }
    }
  • 相关阅读:
    mavenjar 一些拉取不下来问题
    storm运行服务器一些错误解决、
    python操作excel库xlwings---3、写入excel常见操作
    python操作excel库xlwings---2、写入excel基本操作
    python操作excel库xlwings---1、课程介绍
    numpy库常用基本操作
    Numpy库使用总结
    python的xlwings库读写excel操作总结
    numpy中的ndarray与pandas的Series和DataFrame之间的相互转换
    Numpy和Pandas的区别
  • 原文地址:https://www.cnblogs.com/diwu0510/p/10663427.html
Copyright © 2020-2023  润新知