码迷,mamicode.com
首页 > 其他好文 > 详细

表达式目录树

时间:2018-07-22 20:03:17      阅读:202      评论:0      收藏:0      [点我收藏+]

标签:ati   property   com   创建   自己的   books   标识   typeof   lam   

文章目录:

  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

 

表达式目录树

标签:ati   property   com   创建   自己的   books   标识   typeof   lam   

原文地址:https://www.cnblogs.com/ztb123/p/9351113.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!