13ExpressionTree应用场景二

EF框架类似功能:SQL语句解析(条件)

表达式与EF框架

在我们使用EF框架进行筛选等等条件操作或者访问数据库的时候,EF都会将我们传的lambda表达式拼接为相应的表达式树,然后转为数据库认识的SQL语句格式的表达式如:

//EF上下文对象
            StudentDBEntities studentDBEntities = new StudentDBEntities();

            var query = studentDBEntities.Student.Where(s => s.Id > 1 && s.Address.Contains("门"));
            Console.WriteLine(query);

输出结果如下:
image

实际上EF框架里的where方法也是需要一个表达式树作为参数如:
image

我们可以创建一个它所需要的表达式树并且条件和where里的一样:
Expression<Func<Student, bool>> expression = s => s.Id > 1 && s.Address.Contains("门");
我们输出可以看到,它就是一个lambda表达式
image

所以,我们可以通过ExpressionVisitor类里的方法手动的给它拼接成EF框架做的功能,EF框架原理应该也是通过这个类进行重写里面的方法进行拼接的。

我们如果要自己修改表达式树,以此来实现生成我们指定的特殊sql语句表达式,那么,我们可以了解一下ExpressionVisitor类,该类就是用来拼接查询用的表达式的。



ExpressionVisitor类

我认为:这个类用来将传入的表达式树自动拼接为表达式树,所以我们可以在该类的拼接表达式的对应方法里将方法重写,从而实现自己手动拼接复杂的sql条件的表达式,默认这类里面都是很多虚方法,它会将你传的lambda表达式拼接成默认的表达式树,我们

查看该类的内部方法
image
可以看到,这个类里大部分方法都是虚方法,而且这个类里的Visit方法就是根据我们传的表达式去调用对应的方法进行拼接表达式。

于是我们写一个Customevisitor类,继承ExpressionVisitor类,进行测试

 public  class Customevisitor: ExpressionVisitor
    {
        //自动根据传入的表达式调用指定方法进行拼接表达式,它递归的方式调用其它方法的,因为传入的表达式可能要通过多个方法才能进行拼接
        public override Expression Visit(Expression node)
        {
            return base.Visit(node);
        }



        //遇到二元表达式会该方法拼接
        protected override Expression VisitBinary(BinaryExpression node)
        {
            return base.VisitBinary(node);
        }

        //遇到方法表达式会该方法拼接
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            return base.VisitMethodCall(node);
        }


        //遇到常量表达式会该方法拼接
        protected override Expression VisitConstant(ConstantExpression node)
        {
            return base.VisitConstant(node);
        }

    }

然后我们写一个表达式,再调用该类,把它里面的几个方法先重写一下,然后在Visit方法处打个断点,可以发现,我们表达式里有常量表达式和二元表达式和方法表达式,它都会在Visit方法里进行递归调用对应的方法进行拼接表达式树

image

我们可以输出结果,发现,虽然它确实根据我们传入的表达式节点类型通过Visit方法去递归调用了其它对应的方法去拼接表达式树,但是貌似就和没拼接一样,是因为这些虚方法里面是没有实现的,就是为了方便我们自己写实现如:
image



通过修改表达式树,让它变成我们数据库可以识别的sql条件语句就像EF框架那样

我们创建一个ConditionVisitor转换类,让其继承ExpressionVisitor类,在里面重写Visit方法会调用的常用的几个方法,以便于实现像EF那样的转换sql类型的表达式树

 /// <summary>
    /// 条件访问者
    /// </summary>
    public class ConditionVisitor : ExpressionVisitor
    {

        //创建一个栈,用来暂时存修改好的表达式,表达式树的永久性:想修改一个表达式 树,就必须复制这个表达式,然后替换其中的节点来创建要给 新的表达式,可以先定义一个栈来存
        private Stack<string> _stringStack = new Stack<string>();


        //返回栈里面的条件字符串,通过这个方法将需要表达式格式返回回去
        public string Condition()
        {
            //将栈数组转为字符串
            string condition = string.Concat(this._stringStack.ToArray());

            this._stringStack.Clear();//清空栈,确保使用时栈里面是空的

            return condition;

        }



        //最后转换的表达式如下:
        // WHERE ([Extent1].[Id] > 1) AND ([Extent1].[Address] LIKE '%门%')


        //二元表达式
        protected override Expression VisitBinary(BinaryExpression node)
        {

            //栈是先进后出,所以要先右括号,而且Visit解析 都是从右边节点开始往左边解析的
            //任何表达式节点类型都可以分左右两部分,除了常量表达式节点

            this._stringStack.Push(")");
            base.Visit(node.Right);//先解析右边

            //因为是BinaryExpression二元表达式类型,所以node.NodeType会返回对应的二元表达式运算符的对应英文如加就是Add
            this._stringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");

            base.Visit(node.Left);//先解析右边

            this._stringStack.Push(" (");

            //return base.VisitBinary(node);//这里就不返回父类的VisitBinary了,因为我们在上面已经手动拼接好了,而且父类方法里可能没有实现,所以就是默认原格式返回 

            return node;//返回手动拼接好的节点即可

        }

        //成员表达式
        protected override Expression VisitMember(MemberExpression node)
        {
            this._stringStack.Push("[" + node.Member.Name + "]");
            //return base.VisitMember(node);

            return node;

        }

        //常量表达式
        protected override Expression VisitConstant(ConstantExpression node)
        {
            //判断常量节点的值的类型
            Type type = node.Value.GetType();

            if (type.Equals(typeof(string)) || type.Equals(typeof(DateTime)))//如果是字符或者日期,数据库里规定是需要加单引号
            {
                this._stringStack.Push("'" + node.Value + "'");//成员入栈不要有空格,如果有空格值就不对了,因为多了一个空字符串
            }
            else
            {
                this._stringStack.Push(node.ToString());
            }

            //return base.VisitConstant(node);

            return node;
        }


        //方法表达式
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            this.Visit(node.Object);//左边,方法所在的对象,返回成员表达式--》跳转到VisitMember方法

            this.Visit(node.Arguments[0]);//右边 :方法参数,返回常量表达式 -》跳转到VisitConstant方法


            //获取入栈数据,为方法解析做参数(参数和字段名)
            string right = this._stringStack.Pop();
            string left = this._stringStack.Pop();

            //最后转换的表达式如下:
            // WHERE ([Extent1].[Id] > 1) AND ([Extent1].[Address] LIKE '%门%')

            switch (node.Method.Name)
            {
                case "Contains":
                    this._stringStack.Push($"{left} LIKE N '%{right.Replace("'", "")}%'");
                    break;
                case "Equals":
                    this._stringStack.Push($"{left}={right}");
                    break;
                default://还有很多方法可以自己写
                    throw new NotSupportedException(node.NodeType + "不支持");
            }

            return node;
        }

    }

在上面类里的二元表达式方法里会用到一个工厂方法,用于将BinaryExpression的实例node.NodeType转为数据库对应的二元表达式关键字,所以我们创建一个静态类,直接给Expression写一个扩展方法即可

 /// <summary>
    /// 运算符转换类,相当于工厂,比如:=、&&====>and
    /// </summary>
    internal static class SqlHperator
    {


        internal static string ToSqlOperator(this ExpressionType type)
        {
			//判断二元表达式返回类型
            switch (type)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    return "AND";
                    break;

                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    return "OR";
                    break;
                case ExpressionType.Not:
                    return "Not";
                    break;
                case ExpressionType.NotEqual:
                    return "<>";
                    break;
                case ExpressionType.GreaterThan:
                    return ">";
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                    break;
                case ExpressionType.LessThan:
                    return "<";
                    break;

                case ExpressionType.LessThanOrEqual:
                    return "<=";
                    break;

                case ExpressionType.Equal:
                    return "=";
                    break;

                default:
                    throw new Exception("不支持该方法");
                  
            }

        }
    }

我们来调用该类,然后把表达式树放进去测试
image
可以看到,已经转换为对应

posted @ 2021-11-14 17:38  青仙  阅读(66)  评论(0编辑  收藏  举报