zoukankan      html  css  js  c++  java
  • 表达式目录树

    文章目录:

      1、简单的表达式树实现以及声明方式

       2、表达式树条件拼接

       3、表达式树关系映射

       4、表达式树访问者

            5、表达式树扩展

     

    简单介绍表达式树

    相信大家使用EF框架的时候,对实体集延迟查询对象IQueryable一定不陌生,对实体集操作的时候,参数要求传递一个Expression<TDelegate>的泛型类,泛型参数是一个委托Expression;然后Expression<TDelegate>又继承自LambdaExpression抽象类,其父类是Expression抽象类,Expression抽象类就是我们所说的表达式目录树基类,如下图所示。

      

    1、简单的表达式树实现以及声明方式

    首先我们看以下一段代码

    复制代码
    //普通的Lambda表达式
     Func<int,int,int> func = (x,y)=>  x + y - 2;
    //表达式目录树的Lambda表达式声明方式
    Expression<Func<int, int, int>> expression = (x, y) => x + y - 2;   
    //表达式目录树的拼接方式实现
    ParameterExpression parameterx =  Expression.Parameter(typeof(int), "x");
    ParameterExpression parametery =  Expression.Parameter(typeof(int), "y");
    ConstantExpression constantExpression = Expression.Constant(2, typeof(int));
    BinaryExpression binaryAdd = Expression.Add(parameterx, parametery);
    BinaryExpression binarySubtract = Expression.Subtract(binaryAdd, constantExpression);
    Expression<Func<int, int, int>> expressionMosaic = Expression.Lambda<Func<int, int, int>>(binarySubtract, new ParameterExpression[]
    {
           parameterx,
           parametery
    });
    int ResultLambda = func(5, 2);
    int ResultExpression = expression.Compile()(5, 2);
    int ResultMosaic = expressionMosaic.Compile()(5, 2);
    Console.WriteLine($"func:{ResultLambda}");
    Console.WriteLine($"expression:{ResultExpression}");
    Console.WriteLine($"expressionMosaic:{ResultMosaic}");
    复制代码

    上面这段代码分别用普通的委托,表达式目录树的lambda实现方式,表达式目录树的拼接方式实现了两个变量相加再减去一个常量。结果很显然都是5如下图:

    上面两段代码相信就不用解释了,让我们来看一下目录树拼接这段代码,

    ParameterExpression parameterx =  Expression.Parameter(typeof(int), "x");//声明一个参数表达式,int类型,名字叫“x”
    ConstantExpression constantExpression = Expression.Constant(2, typeof(int));//声明一个常量表达式,int类型,值为2
    BinaryExpression binaryAdd = Expression.Add(parameterx, parametery);  //二进制运算符表达式相加
    BinaryExpression binarySubtract = Expression.Subtract(binaryAdd, constantExpression);//二进制运算符表达式相减
    复制代码
    //将表达式树翻译成lambda表达式,并将变量参数传入
    Expression<Func<int, int, int>> expressionMosaic = Expression.Lambda<Func<int, int, int>>(binarySubtract, new ParameterExpression[]
    {
           parameterx,
           parametery
    });
    复制代码
    //编译执行
    int ResultMosaic = expressionMosaic.Compile()(5, 2);

    让我们再来看一个例子

    复制代码
    //目录树的Lambda声明方式
    Expression<Func<Book, bool>> expressionLambda = x => x.Id.ToString().Equals("6");
    //目录树的变量声明拼接方式
    ParameterExpression parameterExpression = Expression.Parameter(typeof(Book), "x"); //声明一个参数表达式,Book类型,名字叫“x”
    Expression<Func<Book, bool>> expression = Expression.Lambda<Func<Book, bool>>(
        Expression.Call(                                                                //Expression.Call创建一个表示带参数的方法调用
            Expression.Call(
                Expression.Property(parameterExpression, typeof(Book).GetProperty("Id")),  //反射拿到Id属性
                typeof(Int32).GetMethod("ToString", new Type[] { }),                              //反射拿到Int类型的Tostring方法
                new Expression[0]),                                                              //这里是没有参数的
            typeof(string).GetMethod("Equals", new Type[] { typeof(string) }),                     //反射拿到String的Equals 方法
            new Expression[]
            {
                Expression.Constant("6",typeof(string))                                          //反射拿到String的Equals 5   
            })
        , new ParameterExpression[]                                                               //最后一个参数,代表传入的book
        {
            parameterExpression
        });
    bool a = expressionLambda.Compile()(new Book { Id = 4, Name = "C#高级编程", Price = 100 });
    bool b = expressionLambda.Compile()(new Book { Id = 6, Name = "CLR Via C#", Price = 100 });
    bool c = expression.Compile()(new Book { Id = 4, Name = "C#高级编程", Price = 100 });
    bool d = expression.Compile()(new Book { Id = 6, Name = "CLR Via C#", Price = 100 });
    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
    Console.WriteLine(d);
    Console.Read();
    复制代码

     

    相信到这里,对表达式目录树拼接有一个基本的认识了,第一个例子这个过程有点像后缀表达式(逆波兰式)的执行。5+2-2 从左往右遍历入栈,遇到运算符出栈运算后入栈。

    2、表达式树条件拼接

     平时业务中,经常要根据用户的输入参数进行数据过滤,下面两种方法我们一起对比一下

    复制代码
    //这里用List 然后AsQueryable转下 偷个懒。。
    List<Book> books = new List<Book> { new Book{Id = 1,Name ="C#高级编程第1版",Price=100}, new Book{Id = 2,Name ="C#高级编程第2版",Price=110}, new Book{Id = 3,Name ="C#高级编程第3版",Price=120}, new Book{Id = 4,Name ="C#高级编程第5版",Price=130}, new Book{Id = 5,Name ="C#高级编程第5版",Price=140}, new Book{Id = 6,Name ="C#高级编程第5版",Price=150}, new Book{Id = 6,Name ="C#高级编程第7版",Price=160}, }; //这里我们查询三个条件 QueryDto query = new QueryDto { Id = 3, Name = "第5版", Price = 140 }; //我们一般用EF查询时候 var entitys = books.AsQueryable(); if (query.Id.HasValue) entitys = entitys.Where(t => t.Id == query.Id.Value); if (!string.IsNullOrEmpty(query.Name)) entitys = entitys.Where(t => t.Name.Contains(query.Name)); if (query.Price.HasValue) entitys = entitys.Where(t => t.Price == query.Price.Value); //表达式树拼接方式 Expression<Func<Book, bool>> expression = t => true; ParameterExpression parameterExpression = Expression.Parameter(typeof(Book), "x"); var ee = typeof(int).GetMethods(); if (query.Id.HasValue) { MemberExpression memberExpressionId = Expression.Property(parameterExpression, typeof(Book).GetProperty("Id")); MethodCallExpression method = Expression.Call(memberExpressionId , typeof(int).GetMethod("Equals", new Type[] { typeof(int) }) , new Expression[] { Expression.Constant(query.Id.Value, typeof(int)) }); expression = Expression.Lambda<Func<Book, bool>>(method, new ParameterExpression[] { parameterExpression }); } if (!string.IsNullOrEmpty(query.Name)) { MemberExpression memberExpressionName = Expression.Property(parameterExpression, typeof(Book).GetProperty("Name")); MethodCallExpression method = Expression.Call(memberExpressionName , typeof(string).GetMethod("Contains", new Type[] { typeof(string) }) , new Expression[] { Expression.Constant(query.Name, typeof(string)) }); expression = Expression.Lambda<Func<Book, bool>>(method, new ParameterExpression[] { parameterExpression }); } if (query.Price.HasValue) { MemberExpression memberExpressionId = Expression.Property(parameterExpression, typeof(Book).GetProperty("Price")); MethodCallExpression method = Expression.Call(memberExpressionId , typeof(double).GetMethod("Equals", new Type[] { typeof(double) }) , new Expression[] { Expression.Constant(query.Price.Value, typeof(double)) }); expression = Expression.Lambda<Func<Book, bool>>(method, new ParameterExpression[] { parameterExpression }); } var entity = books.AsQueryable().Where(expression);
    复制代码

    大家可以对比下这两种方式那种比较好。第二种虽然要多写代码,但是防止了数据暴露的风险。

    这里借鉴     腾飞(Jesse)前辈的几张图,博客地址=》  http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html 

     

    3、表达式树关系映射

    平时工作中,经常会有这样的需求,就是数据库实体映射为DTO,返回给前端。例如这里的Book是我们的数据库实体类,BookCopy 是Dto类

    复制代码
        public class Book
        {
            [Key]
            public int Id { get; set; }
            public string Name { get; set; }
            public double Price { get; set; }
        }
        public class BookCopy
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public double Price { get; set; }
        }
    复制代码

    最简单粗暴的方法应该是这样。

    复制代码
    //写死的属性映射
    Book book = new Book
    {
        Id = 1,
        Name = "C#高级编程",
        Price = 100.00
    };
    BookCopy bookCopy = new BookCopy
    {
        Id = book.Id,
        Name = book.Name,
        Price = book.Price
    };
    复制代码
    复制代码
    //目录树的方式
    Expression<Func<Book, BookCopy>> expression = b =>
        new BookCopy
        {
            Id = b.Id,
            Name = b.Name,
            Price = b.Price
        };
    复制代码

    但是这样写的话特别不灵活,还是写死的。假如我们的Book类又添加一个属性"Press",那又得改动代码,所以我们可以用反射的放射+表达式树参数拼接来实现

    复制代码
    //参数拼接的方式
    ParameterExpression parameterExpression = Expression.Parameter(typeof(Book), "book");
    List<MemberBinding> memberbindingList = new List<MemberBinding>(); //表示绑定的类派生自的基类,这些绑定用于对新创建对象的成员进行初始化(vs的注解。太生涩了,我这样的小白解释不了,大家将就着看)
    foreach (var item in typeof(BookCopy).GetProperties())             //遍历BookCopy的所有属性
    {
        MemberExpression property = Expression.Property(parameterExpression, typeof(Book).GetProperty(item.Name));//拿到Book的这个属性
        MemberBinding memberBinding = Expression.Bind(item, property);                                            //初始化这个属性
        memberbindingList.Add(memberBinding);
    }
    foreach(var item in typeof(BookCopy).GetFields())
    {
        MemberExpression filed = Expression.Field(parameterExpression, typeof(Book).GetField(item.Name));//拿到book的这个字段,这里book类没有字段。。
        MemberBinding memberBinding = Expression.Bind(item, filed);
        memberbindingList.Add(memberBinding);
    }
    MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(BookCopy)), memberbindingList);//初始化创建新对象
    Expression<Func<Book,BookCopy>> ExpressionRun = Expression.Lambda<Func<Book, BookCopy>>(memberInitExpression, new ParameterExpression[]{
        parameterExpression
    });
    var ExpressionbookCopy = ExpressionRun.Compile()(book);
    复制代码

    结果如上图所示,但是问题又来了,我们不可能只有一个类,也不可能只有一个Dto,那我们应该怎么实现呢? 对 ,可以用泛型来实现

    复制代码
    public class ExpressionMapper<TIn,TOut>
    {
        public static Func<TIn, TOut> _FuncCatch = null;                    //我们这里利用静态类的特性作为一个缓存,静态类跟随类的初始化创建,而且有CLR保证单例的
        static ExpressionMapper()
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "entity");
            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 filed = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, filed);
                memberbindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberbindingList);
            Expression<Func<TIn, TOut>> ExpressionRun = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]{
                    parameterExpression
                });
            _FuncCatch = ExpressionRun.Compile();                   //静态构造函数,每一次只有Tin或者Tout不同的时候才会创建新的变量_FuncCatch
        }
        public static TOut Mapping(TIn t)                           //取出TOut,静态变量是一个Func,参数是传入的Tin 实例
        {
            return _FuncCatch(t);
        }
    复制代码

    让我们来测试一波性能如何=》

    4、表达式树访问者

     

    这里先让我们看下IQueryable对象,IQueryable有三个属性,Provider属性就是拿到当前解析的表达式树,并将表达式树交给Expression(个人理解,不对地方希望大家指正)。

    C#给我们提供了一个抽象类,ExpressionVisitor,我们可以通过这个继承抽象类,重写这个抽象类的方法来访问表达式树中的各个节点,达到自己预定义的效果

    复制代码
    public class MyOperationVisitor : ExpressionVisitor             //定义自己的表达式树访问者类,继承ExpressionVisitor抽象类
    {
        public Expression Modify(Expression expression)             //对外公开的方法
        {
            return this.Visit(expression);
        }
        protected override Expression VisitBinary(BinaryExpression node) //假如这里是个二元运算,代码运行我们重写VisitBinary方法的逻辑
        {
            if(node.NodeType == ExpressionType.Add)                      //假如这里是个加法运算我们给它改成一个减法
            {
                Expression left = this.Visit(node.Left);
                Expression right = this.Visit(node.Right);
                return Expression.Subtract(left, right);
            }
            if(node.NodeType == ExpressionType.LessThan)                //假如这里是个<运算我们给它改成>
            {
                Expression left = this.Visit(node.Left);
                Expression right = this.Visit(node.Right);
                return Expression.GreaterThan(left, right);
            }
            return base.VisitBinary(node);
        }
        protected override Expression VisitConstant(ConstantExpression node)    //假如节点中存在常量,我们打印个hahahah
        {
            Console.WriteLine("hahahah");
            return base.VisitConstant(node);
        }
    }
    复制代码

    这里的  x*y+2目录树的访问方式就像右图我画的那样,二叉树的中序遍历。这里再借用腾飞前辈博客里面的一张图

    接下来,趁热打铁,让我们来根据表达式树的条件生成自定义SQL脚本===================华丽的分割线====================================================

    复制代码
    public class MyConditionVisitor: ExpressionVisitor
    {
        private Queue<string> _queueCommand = new Queue<string>();          //这里我们用一个队列来保存生成的脚本
    
        public string Condition()                                           //返回脚本
        {
            string condition = string.Concat(_queueCommand.ToArray());
            this._queueCommand.Clear();
            return condition;
        }
        /// <summary>
        /// 处理二元表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node == null)
                throw new ArgumentException("BinaryExpression");
            this._queueCommand.Enqueue("(");
            base.Visit(node.Left);
            this._queueCommand.Enqueue($" {node.NodeType.ToSqlCommandString()} ");
            base.Visit(node.Right);
            this._queueCommand.Enqueue(")");
            return node;
        }
        /// <summary>
        /// 访问每一个成员
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMember(MemberExpression node)
        {
            if(node ==null)
                throw new ArgumentException("MemberExpression");
            this._queueCommand.Enqueue($"[{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");
            switch (node.Value.GetType().Name)
            {
                case "String":
                    this._queueCommand.Enqueue($" ' {node.Value} '");
                    break;
                case "Boolean":
                    this._queueCommand.Enqueue((bool)node.Value ? "1" : "0");
                    break;
                default:
                    this._queueCommand.Enqueue(node.Value.ToString());
                    break;
            }
            return node;
        }
        /// <summary>
        /// 方法表达式
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
    
            if (node == null)
                throw new ArgumentNullException("MethodCallExpression");
            string format = string.Empty;
            switch (node.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(node.NodeType + " is not supported!");
            }
            this.Visit(node.Object);
            this.Visit(node.Arguments[0]);
            this._queueCommand.Enqueue(string.Format(format, node.Object, node.Arguments[0]));
            return node;
        }
    }
    复制代码

    这里我们把二元运算符用扩展方法的形式标识,代码如下

    复制代码
    /// <summary>
    /// 使用一个扩展方法处理二元运算符
    /// </summary>
    public static class CommandCreaterHelper
    {
        public static string ToSqlCommandString(this ExpressionType type)
        {
            switch (type)
            {
                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("不支持该方法");
            }
        }
    }
    复制代码

    让我们来测试下、、

    5、表达式树扩展

     1 /// <summary>
     2     /// 合并表达式 And Or  Not扩展
     3     /// </summary>
     4     public static class ExpressionExtend
     5     {
     6         /// <summary>
     7         /// 合并表达式 expr1 AND expr2
     8         /// </summary>
     9         /// <typeparam name="T"></typeparam>
    10         /// <param name="expr1"></param>
    11         /// <param name="expr2"></param>
    12         /// <returns></returns>
    13         public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    14         {
    15             //return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
    16             ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
    17             NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
    18 
    19             var left = visitor.Replace(expr1.Body);
    20             var right = visitor.Replace(expr2.Body);
    21             var body = Expression.And(left, right);
    22             return Expression.Lambda<Func<T, bool>>(body, newParameter);
    23 
    24         }
    25         /// <summary>
    26         /// 合并表达式 expr1 or expr2
    27         /// </summary>
    28         /// <typeparam name="T"></typeparam>
    29         /// <param name="expr1"></param>
    30         /// <param name="expr2"></param>
    31         /// <returns></returns>
    32         public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    33         {
    34 
    35             ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
    36             NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
    37 
    38             var left = visitor.Replace(expr1.Body);
    39             var right = visitor.Replace(expr2.Body);
    40             var body = Expression.Or(left, right);
    41             return Expression.Lambda<Func<T, bool>>(body, newParameter);
    42         }
    43         public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
    44         {
    45             var candidateExpr = expr.Parameters[0];
    46             var body = Expression.Not(expr.Body);
    47 
    48             return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
    49         }
    50     }
    51 
    52 
    53 
    54 
    55 /// <summary>
    56     /// 建立新表达式
    57     /// </summary>
    58     internal class NewExpressionVisitor : ExpressionVisitor
    59     {
    60         public ParameterExpression _NewParameter { get; private set; }
    61         public NewExpressionVisitor(ParameterExpression param)
    62         {
    63             this._NewParameter = param;
    64         }
    65         public Expression Replace(Expression exp)
    66         {
    67             return this.Visit(exp);
    68         }
    69         protected override Expression VisitParameter(ParameterExpression node)
    70         {
    71             return this._NewParameter;
    72         }
    73     }

    相关链接 

    https://www.cnblogs.com/castyuan/category/1033520.html

    https://www.cnblogs.com/liumengchen-boke/p/7900766.html

  • 相关阅读:
    如何判断retina,如何判断设备的比例
    说说移动前端中 viewport (视口)
    随机生成广告
    fullPage 全屏滚动【上下滚动】效果
    判断鼠标滚动方向
    低版本IE浏览器 input元素出现叉叉的情况
    关于IE7 默认有边框的解决方案
    embed 层级太高
    搜狐-新闻页 粗略整理-自我学习
    页面重构布局样式命名规则 —— 参考
  • 原文地址:https://www.cnblogs.com/saodiseng2015/p/9351113.html
Copyright © 2011-2022 走看看