表达式树动态拼接lambda 动态拼接lambda表达式树

表达式树动态拼接lambda
动态拼接lambda表达式树

 

前言

  最近在优化同事写的代码(我们的框架用的是dapperLambda),其中有一个这样很普通的场景——界面上提供了一些查询条件框供用户来进行过滤数据。由于dapperLambda按条件查询时是传入表达式树的参数,这样比如其中查询条件有一个是审核状态,另外五个是模糊查询,那这查询时的表达式树参数就要写两次,这样使得代码看起来有很多是重复的,而且如果查询条件多的情况下,在写那表达式树参数时也容易漏写或错写。所以我在想如果可以动态拼接这表达式树,那这代码就要精简很多了。

 正文

  也许我的上面文字描述让你不明觉里,那下面我就配以简单的代码来再说明一下这个问题:

表达式树动态拼接lambda
动态拼接lambda表达式树
1 Expression<Func<SysUser, bool>> exp1 = s => s.UserName.Contains("1") && s.Age > 10;
2 Expression<Func<SysUser, bool>> exp2 = s => s.UserName.Contains("1") && s.Age > 10 && s.IsEnable == 1;
3 using (var context = new DbContext().ConnectionString(connString))
4 {
5     var result1 = context.Select<SysUser>(exp1).QueryMany();
6     var result2 = context.Select<SysUser>(exp2).QueryMany();
7 }
表达式树动态拼接lambda
动态拼接lambda表达式树

  上面代码两次查询,第一次查询结果result1的结果是用户名中包含1而且年龄大于10,而结果result2中就是在查询结果result1的条件上再加上帐号生效这一条件。所以我们看到exp1和exp2的表达式树绝大部分是相同的,在这个示例里看着这点重复代码似乎还是可以接受的,但是在真实环境下,重复的代码就不止这一点点了。因为exp2和exp1相比,exp2只是在exp1之上多加了一个条件,那我们有没有方法可以在exp1的基础上再加一个条件赋值给exp2呢?答案当然是可以的。

  表达式扩展类:

表达式树动态拼接lambda
动态拼接lambda表达式树
 1 public static class ExpressionExt
 2 {
 3     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
 4     {          
 5         return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
 6     }
 7     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,Expression<Func<T, bool>> expr2)
 8     {
 9         return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, expr2.Body), expr1.Parameters);
10     }
11 }
表达式树动态拼接lambda
动态拼接lambda表达式树

  上面这代码是我们这文章的核心,我在这里顺便解释一下这个And扩展方法它是怎么做的,它这里先是使用Expression类中的静态方法AndAlso把expr1和expr2的主体拼接在一起,如果AndAlso方法返回的是BinaryExpression类型的结果,而dapperLambda的条件参数需要的是Lambda表达式树,所以这里我们需要通过Expression.Lambda方法来构造一个委托类型来创建一个Lambda表达树。

  那现在我们通过上面的扩展方法,再来优化一下我们最初举的例子看下:

表达式树动态拼接lambda
动态拼接lambda表达式树
1 Expression<Func<SysUser, bool>> exp1 = s => s.UserName.Contains("1") && s.Age > 0;
2 Expression<Func<SysUser, bool>> exp2 =exp1.And( s => s.IsEnable == 1);
3 using (var context = new DbContext().ConnectionString(connString))
4 {
5     var result1 = context.Select<SysUser>(exp1).QueryMany();
6     var result2 = context.Select<SysUser>(exp2).QueryMany();
7 }
表达式树动态拼接lambda
动态拼接lambda表达式树

结束语

 虽然这个扩展方法就只有几行代码,但是如果少了这几行代码,在我们的代码里可能就要上了几十行、几百行代码了。有时解决问题的关键就是那么一个不起眼的东西,但就是这么一个不起眼的东西就能帮我们解决一些大问题。

  说到这,让我想起了一句话——每多学一点知识,就少写一行代码。

前言

  最近在优化同事写的代码(我们的框架用的是dapperLambda),其中有一个这样很普通的场景——界面上提供了一些查询条件框供用户来进行过滤数据。由于dapperLambda按条件查询时是传入表达式树的参数,这样比如其中查询条件有一个是审核状态,另外五个是模糊查询,那这查询时的表达式树参数就要写两次,这样使得代码看起来有很多是重复的,而且如果查询条件多的情况下,在写那表达式树参数时也容易漏写或错写。所以我在想如果可以动态拼接这表达式树,那这代码就要精简很多了。

 正文

  也许我的上面文字描述让你不明觉里,那下面我就配以简单的代码来再说明一下这个问题:

表达式树动态拼接lambda
动态拼接lambda表达式树
1 Expression<Func<SysUser, bool>> exp1 = s => s.UserName.Contains("1") && s.Age > 10;
2 Expression<Func<SysUser, bool>> exp2 = s => s.UserName.Contains("1") && s.Age > 10 && s.IsEnable == 1;
3 using (var context = new DbContext().ConnectionString(connString))
4 {
5     var result1 = context.Select<SysUser>(exp1).QueryMany();
6     var result2 = context.Select<SysUser>(exp2).QueryMany();
7 }
表达式树动态拼接lambda
动态拼接lambda表达式树

  上面代码两次查询,第一次查询结果result1的结果是用户名中包含1而且年龄大于10,而结果result2中就是在查询结果result1的条件上再加上帐号生效这一条件。所以我们看到exp1和exp2的表达式树绝大部分是相同的,在这个示例里看着这点重复代码似乎还是可以接受的,但是在真实环境下,重复的代码就不止这一点点了。因为exp2和exp1相比,exp2只是在exp1之上多加了一个条件,那我们有没有方法可以在exp1的基础上再加一个条件赋值给exp2呢?答案当然是可以的。

  表达式扩展类:

表达式树动态拼接lambda
动态拼接lambda表达式树
 1 public static class ExpressionExt
 2 {
 3     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
 4     {          
 5         return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
 6     }
 7     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,Expression<Func<T, bool>> expr2)
 8     {
 9         return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, expr2.Body), expr1.Parameters);
10     }
11 }
表达式树动态拼接lambda
动态拼接lambda表达式树

  上面这代码是我们这文章的核心,我在这里顺便解释一下这个And扩展方法它是怎么做的,它这里先是使用Expression类中的静态方法AndAlso把expr1和expr2的主体拼接在一起,如果AndAlso方法返回的是BinaryExpression类型的结果,而dapperLambda的条件参数需要的是Lambda表达式树,所以这里我们需要通过Expression.Lambda方法来构造一个委托类型来创建一个Lambda表达树。

  那现在我们通过上面的扩展方法,再来优化一下我们最初举的例子看下:

表达式树动态拼接lambda
动态拼接lambda表达式树
1 Expression<Func<SysUser, bool>> exp1 = s => s.UserName.Contains("1") && s.Age > 0;
2 Expression<Func<SysUser, bool>> exp2 =exp1.And( s => s.IsEnable == 1);
3 using (var context = new DbContext().ConnectionString(connString))
4 {
5     var result1 = context.Select<SysUser>(exp1).QueryMany();
6     var result2 = context.Select<SysUser>(exp2).QueryMany();
7 }
表达式树动态拼接lambda
动态拼接lambda表达式树

结束语

 虽然这个扩展方法就只有几行代码,但是如果少了这几行代码,在我们的代码里可能就要上了几十行、几百行代码了。有时解决问题的关键就是那么一个不起眼的东西,但就是这么一个不起眼的东西就能帮我们解决一些大问题。

  说到这,让我想起了一句话——每多学一点知识,就少写一行代码。