C# 中的表达式树

前言:

我们在工作当中,经常会接触到与表达式树相关的内容,比如 EF,EF Core中的 lambda,就是表达式树。
表达式树是一种数据结构,一个能拼装能解析的数据结构。

一、使用lambda声明表达式树

以下代码演示声明表达式树和表达式树转换为委托。

    // lambda表达式 声明表达式目录树,是一个数据结构 (快捷方式)
    Expression<Func<int, int, int>> expr = (m, n) => (m * n) + 2;
    // 表达式目录树 转换为 委托 
    Func<int, int, int> func = expr.Compile();

二、表达式树的分解

先来一个简单的实例,该实例只包含了简单的运算。

	// lambda表达式 声明
    Expression<Func<int, int, int>> expr = (m, n) => m * n + m + n + 2;
    // 常规方式 声明
    ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
    ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
    BinaryExpression binaryExpression = Expression.Multiply(parameterExpression, parameterExpression2);
    BinaryExpression BinaryExpression2 = Expression.Add(binaryExpression, parameterExpression);
    BinaryExpression BinaryExpression3 = Expression.Add(BinaryExpression2, parameterExpression2);
    ConstantExpression constantExpression = Expression.Constant(2, typeof(int));
    BinaryExpression body = Expression.Add(BinaryExpression3, constantExpression);
    ParameterExpression[] array = new ParameterExpression[2];
    array[0] = parameterExpression;
    array[1] = parameterExpression2;
    Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(body, array);

再来一个进阶版的实例。

    // lambda表达式目录树 的分解(2)
    Expression<Func<User, bool>> expr = x => x.Id.ToString().Equals("5");
    
    User user = new User();
    ParameterExpression parameterExpression = Expression.Parameter(typeof(User), "x");
    var constantExp = Expression.Constant("5", typeof(string));
    FieldInfo userId = typeof(User).GetField("Id");
    MemberExpression fieldExp = Expression.Field(parameterExpression, userId);
    MethodInfo toString = user.Id.GetType().GetMethod("ToString", new Type[] {
    
     });
    var toStringExp = Expression.Call(fieldExp, toString, Array.Empty<Expression>());
    var equals = typeof(string).GetMethod("Equals", new Type[] {
    
     typeof(string) });
    var equalsExp = Expression.Call(toStringExp, equals, new Expression[] {
    
     constantExp });
    ParameterExpression[] array = new ParameterExpression[1];
    array[0] = parameterExpression;
    Expression<Func<User, bool>> expression = Expression.Lambda<Func<User, bool>>(equalsExp, array);

三、表达式树实现实体映射

映射的代码:

    public class ExpressionGenericMapper<TIn, TOut>
    {
    
    
        private static Func<TIn, TOut> _func = null;

        static ExpressionGenericMapper()
        {
    
    
            ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
            List<MemberBinding> memberBindingList = new List<MemberBinding>();
            foreach (var item in typeof(TOut).GetProperties())
            {
    
    
                MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            foreach (var item in typeof(TOut).GetFields())
            {
    
    
                MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
            Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
            {
    
    
                    parameterExpression
            });
            _func = lambda.Compile();//拼装是一次性的
        }
        public static TOut Trans(TIn t)
        {
    
    
            return _func(t);
        }
    }

映射:

    // 实体映射 
    var user = new User() {
    
     Id = 1, Name = "111", Age = 12 };
    var userDto = ExpressionGenericMapper<User, UserDto>.Trans(user);

四、表达式树的访问或重写

表达式树的访问或重写需要继承 ExpressionVisitor 类,重写相关方法。
以下是示例:

    public class OperationsVisitor : ExpressionVisitor
    {
    
    
        public override Expression Visit(Expression node)
        {
    
    
            Console.WriteLine($"Visit {node}");
            return base.Visit(node);
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
    
    
            if (node.NodeType == ExpressionType.Add)
            {
    
    
                var left = Visit(node.Left);
                var right = Visit(node.Right);
                // 将 相加 变成 相减.
                return Expression.Subtract(left, right);
            }
            else if (node.NodeType == ExpressionType.Multiply)
            {
    
    
                var left = Visit(node.Left);
                var right = Visit(node.Right);
                // return Expression.Divide(left, right);

                return Expression.Divide(node.Left, node.Right);
            }

            return base.VisitBinary(node);
        }
	}

调用方法:

    // 表达式树的访问或重写 (ExpressionVisitor)
    Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
    OperationsVisitor visitor = new OperationsVisitor();
    // 修改表达式数
    Expression<Func<int, int, int>> expNew = visitor.Visit(exp) as Expression<Func<int, int, int>>;
    var a = expNew.Compile()(8, 4);
    // expNew 为 (m, n) => m / n - 2;
    // a 为 0

五、表达式树的Sql拼装

    public class OperationsVisitor : ExpressionVisitor
    {
    
    
        private readonly Stack<string> stack = new Stack<string>();

        public string Condition()
        {
    
    
            var condition = string.Concat(stack.ToArray());
            stack.Clear();
            return condition;
        }

        private string ToSqlOperator(ExpressionType type)
        {
    
    
            switch (type)
            {
    
    
                case (ExpressionType.AndAlso):
                    return "AND";
                case ExpressionType.GreaterThan:
                    return ">";
                case (ExpressionType.Equal):
                    return "=";
                default:
                    throw new Exception("不支持该方法");
            }
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
    
    
            Console.WriteLine($"VisitMember {node}");

            if (node == null)
                throw new ArgumentNullException("BinaryExpression");

            stack.Push(")");
            Visit(node.Right);
            stack.Push($" {ToSqlOperator(node.NodeType)} ");
            Visit(node.Left);
            stack.Push("(");

            return node;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
    
    
            if (node == null)
                throw new ArgumentNullException("MemberExpression");

            stack.Push($"[{node.Member.Name}]");
            return node;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
    
    
            if (node == null)
                throw new ArgumentNullException("ConstantExpression");

            stack.Push($"{node.Value}");
            return node;
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
    
    
            if (node == null)
                throw new ArgumentNullException("MethodCallExpression");

            Visit(node.Object);
            Visit(node.Arguments[0]);

            string right = stack.Pop();
            string left = stack.Pop();

            switch (node.Method.Name)
            {
    
    
                case "StartsWith":
                    stack.Push($"({left} LIKE '{right}%')");
                    break;
                default:
                    throw new NotSupportedException($"{node.NodeType} is not supported!");
            }

            return node;
        }
    }

调用时:

    Expression<Func<User, bool>> exp = x => x.Name.StartsWith("鹿") && x.Age > 5 && x.Id == 10;
    OperationsVisitor visitor = new OperationsVisitor();
    visitor.Visit(exp);
    var sqlWhere = visitor.Condition();
    // ((([Name] LIKE '鹿%') AND ([Age] > 5)) AND ([Id] = 10))

猜你喜欢

转载自blog.csdn.net/Upgrader/article/details/107447644
今日推荐