c#表达式目录树底层源码剖析,Lambda转Sql的全过程

一、定义解析Lambda实体类

!!!注意: 一定要继承 ExpressionVisitor 解析表达式目录树的抽象类,不然咱自己定义的方法不可使用

二、定义解析需要的方法

1、定义一个解析存储sql语句的栈(使用先进后出的逻辑)
    /// <summary>







    /// Where语句存储栈
    /// 使用先进后出原则
    /// </summary>


    private Stack<string> _stack = new Stack<string>();
2、重写解析二元表达式方法: VisitBinary (例如: X.Id > 8)
    /// <summary>







    /// 解析二元表达式
    /// </summary>




    /// <param name="node"></param>

    /// <returns></returns>


    protected override Expression VisitBinary(BinaryExpression node)
    {

        if (node == null) //如果解析的节点是空,抛出异常
            throw new ArgumentNullException(nameof(node) + ": 为空!");



        this._stack.Push(")");//添加where语句最右侧的括号
        base.Visit(node.Right);//解析右边

        //解析拼接条件 例: And  Or  >  <  >=  <=  =  Not
        this._stack.Push($" {MyExpressionVisitor.GetYymbol(node.NodeType)}");

        base.Visit(node.Left);//解析左边
        this._stack.Push("(");//添加where语句最左侧括号


        return node;
    }

GetYymbol这个方法是我自己定义的,用来把lambda的符号,如 ‘&&‘ 转译成sql语句中对应的符号’And

代码如下:

    /// <summary>







    /// 获取简单条件拼接符号
    /// </summary>




    /// <param name="expType"></param>
    /// <returns></returns>


    public static string GetYymbol(ExpressionType expType)
    {

        switch(expType)
        {
            case (ExpressionType.AndAlso):
            case (ExpressionType.And):
                return "AND";
            case (ExpressionType.OrElse):
            case (ExpressionType.Or):
                return "OR";
            case (ExpressionType.Not):
                return "NOT";
            case (ExpressionType.NotEqual):
                return "<>";
            case ExpressionType.GreaterThan:
                return ">";
            case ExpressionType.GreaterThanOrEqual:
                return ">=";
            case ExpressionType.LessThan:
                return "<";
            case ExpressionType.LessThanOrEqual:
                return "<=";
            case (ExpressionType.Equal):
                return "=";
            default:
                throw new Exception("Lanbda存在不识别的符号:" + expType.ToString());
        }
    }
3、重写解析属性的方法: VisitMember (例如: X.Id)
    /// <summary>







    /// 重写解析属性 例: x.Id => Id
    /// 向where语句存储栈中添加解析成功的属性 例如: Id
    /// </summary>


    /// <param name="node">属性,例: x.Id</param>
    /// <returns></returns>


    protected override Expression VisitMember(MemberExpression node)
    {


        if(node == null) throw new ArgumentNullException(nameof(node)+ ": 为空!");



        this._stack.Push(node.Member.Name);


        return node;

    }

4、重写解析常量的方法: VisitConstant (例如: 数字1 或者 字符串”王”)
    /// <summary>







    /// 重写解析常量 例如: 1
    /// </summary>




    /// <param name="node"></param>

    /// <returns></returns>


    /// <exception cref="ArgumentNullException"></exception>
    protected override Expression VisitConstant(ConstantExpression node)
    {


        if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");



        this._stack.Push($"'{node.Value}'");


        return node;

    }

5、重写解析系统方法的方法: VisitMethodCall (例如: x.Name.Contains(“abc”) )
    /// <summary>







    /// 重写解析方法
    /// 获取复杂条件拼接字符串
    /// </summary>


    /// <param name="node">方法,例:x.Name.Contains("abc"),x.Number.IsNullOrEmpty()......</param>
    /// <returns></returns>


    /// <exception cref="ArgumentNullException"></exception>
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");


        string result = null;
        switch (node.Method.Name)
        {

            case "StartsWith":
                result = "({0} LIKE '{1}%')";
                break;


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

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

            default:
                throw new NotSupportedException("存在不识别方法: " + node.NodeType);
        }
        //解析属性 例: x.Name
        this.Visit(node.Object);
        //解析方法中的参数, 例: Contains("abc")中的 abc
        this.Visit(node.Arguments[0]);
        string right = this._stack.Pop();//在栈中剪切出来一个右侧  abc
        string left = this._stack.Pop(); //在栈中剪切出来一个左侧  name
        //重新拼接,存储到栈中
        this._stack.Push(String.Format(result, left, right));

        return node;
    }

到这里我们就简单的定义好解析的方法了

三、生成Sql语句字符串

把最上方,栈中存储的数据转成字符串

    /// <summary>







    /// 获取Lambda转译成的where语句
    /// </summary>




    /// <returns></returns>
    private string StackToString()
    {
        string result = string.Concat(this._stack.ToArray());//把栈数据转译字符串
        this._stack.Clear();   //清空栈
        return result;
    }

四、封装调用方法

我在这里封装了一个参数为Lambad表达式,返回值为字符串的泛型方法

    /// <summary>







    /// 获取where字符串语句
    /// </summary>




    /// <typeparam name="T"></typeparam>
    /// <param name="expression"></param>
    /// <returns></returns>


    public static string FindWhere<T>(Expression<Func<T,bool>> expression)
    {


        MyExpressionVisitor myExpression = new MyExpressionVisitor();
        myExpression.Visit(expression);


        return myExpression.StackToString();
    }

五、方法调用

我在这里简单写了一个调用方法

        //调用FindWhereInfo示例
        string sql = $@"SELECT *  FROM [dbo].[Users] ";
        string where = MyExpressionVisitor.FindWhere<Users>(x => x.UserName.Contains("小王") && x.Password == "123");
        if (!string.IsNullOrEmpty(where))
        {
           sql += " WHERE " + where;
        }
        Console.WriteLine(sql);
        

image.png
如图所示,可以看到我们生成的sql语句跟在数据库中写的一模一样,代表我们写的代码非常成功

下面我将封装的全部代码奉上,供大家使用!!!
    using Microsoft.SqlServer.Server;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Common.Expressions
    {
        /// <summary>
        /// 解析Lambda表达式 => Sql语句的Where条件
        /// </summary>
        public class MyExpressionVisitor : ExpressionVisitor
        {
            /// <summary>
            /// Where语句存储栈
            /// 使用先进后出原则
            /// </summary>
            private Stack<string> _stack = new Stack<string>();
    
            /// <summary>
            /// 获取where字符串语句
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="expression"></param>
            /// <returns></returns>
            public static string FindWhere<T>(Expression<Func<T,bool>> expression)
            {
                MyExpressionVisitor myExpression = new MyExpressionVisitor();
                myExpression.Visit(expression);
    
                return myExpression.StackToString();
            }
    
            /// <summary>
            /// 获取Lambda转译成的where语句
            /// </summary>
            /// <returns></returns>
            private string StackToString()
            {
                string result = string.Concat(this._stack.ToArray());//把栈数据转译字符串
                this._stack.Clear();   //清空栈
                return result;
            }
    
            /// <summary>
            /// 解析二元表达式
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            protected override Expression VisitBinary(BinaryExpression node)
            {
                if (node == null) //如果解析的节点是空,抛出异常
                    throw new ArgumentNullException(nameof(node) + ": 为空!");
    
                this._stack.Push(")");//添加where语句最右侧的括号
                base.Visit(node.Right);//解析右边
    
                //解析拼接条件 例: And  Or  >  <  >=  <=  =  Not
                this._stack.Push($" {MyExpressionVisitor.GetYymbol(node.NodeType)}");
    
                base.Visit(node.Left);//解析左边
                this._stack.Push("(");//添加where语句最左侧括号
    
                return node;
            }
    
            /// <summary>
            /// 获取简单条件拼接符号
            /// </summary>
            /// <param name="expType"></param>
            /// <returns></returns>
            public static string GetYymbol(ExpressionType expType)
            {
                switch(expType)
                {
                    case (ExpressionType.AndAlso):
                    case (ExpressionType.And):
                        return "AND";
                    case (ExpressionType.OrElse):
                    case (ExpressionType.Or):
                        return "OR";
                    case (ExpressionType.Not):
                        return "NOT";
                    case (ExpressionType.NotEqual):
                        return "<>";
                    case ExpressionType.GreaterThan:
                        return ">";
                    case ExpressionType.GreaterThanOrEqual:
                        return ">=";
                    case ExpressionType.LessThan:
                        return "<";
                    case ExpressionType.LessThanOrEqual:
                        return "<=";
                    case (ExpressionType.Equal):
                        return "=";
                    default:
                        throw new Exception("Lanbda存在不识别的符号:" + expType.ToString());
                }
            }
        
            /// <summary>
            /// 重写解析属性 例: x.Id => Id
            /// 向where语句存储栈中添加解析成功的属性 例如: Id
            /// </summary>
            /// <param name="node">属性,例: x.Id</param>
            /// <returns></returns>
            protected override Expression VisitMember(MemberExpression node)
            {
                if(node == null) throw new ArgumentNullException(nameof(node)+ ": 为空!");
    
                this._stack.Push(node.Member.Name);
    
                return node;
            }
    
            /// <summary>
            /// 重写解析常量 例如: 1
            /// </summary>
            /// <param name="node"></param>
            /// <returns></returns>
            /// <exception cref="ArgumentNullException"></exception>
            protected override Expression VisitConstant(ConstantExpression node)
            {
                if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");
    
                this._stack.Push($"'{node.Value}'");
    
                return node;
            }
    
            /// <summary>
            /// 重写解析方法
            /// 获取复杂条件拼接字符串
            /// </summary>
            /// <param name="node">方法,例:x.Name.Contains("abc"),x.Number.IsNullOrEmpty()......</param>
            /// <returns></returns>
            /// <exception cref="ArgumentNullException"></exception>
            protected override Expression VisitMethodCall(MethodCallExpression node)
            {
                if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");
    
                string result = null;
                switch (node.Method.Name)
                {
    
                    case "StartsWith":
                        result = "({0} LIKE '{1}%')";
                        break;
    
                    case "Contains":
                        result = "({0} LIKE '%{1}%')";
                        break;
    
                    case "EndsWith":
                        result = "({0} LIKE '%{1}')";
                        break;
    
                    case "IsNullOrEmpty":
                        result = "({0} Is Null)";
                        break;
    
                    default:
                        throw new NotSupportedException("存在不识别方法: " + node.NodeType);
                }
                //解析属性 例: x.Name
                this.Visit(node.Object);
                //解析方法中的参数, 例: Contains("abc")中的 abc
                this.Visit(node.Arguments[0]);
                string right = this._stack.Pop();//在栈中剪切出来一个右侧  abc
                string left = this._stack.Pop(); //在栈中剪切出来一个左侧  name
                //重新拼接,存储到栈中
                this._stack.Push(String.Format(result, left, right));
    
                return node;
            }
    
        }
    }

好了,今天的分享就到这里来,大家有对表达式目录树更深的见解,欢迎到评论区讨论1!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY5m2bBH' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片