【.net lambda表达式合并】事情的起因是公司一个小伙子问了我个问题 “海哥,来帮我看下这段代码怎么不行”
Func<Report,bool> nameFilter = x=>x.Name == "test";DbContext.Report.Where(x=>x.State==1 && nameFilter(x));
我一看 , 好家伙,这么骚的代码都能让你想出来 , 正常情况下用Linq To Object是可以这么操作的 , 但是EF的IQueryable
查询是不能这么操作的 。Linq To Object是直接执行表达式,他就是个委托方法,里面嵌套多少层委托和方法都是能直接执行的IQueryable
并不会执行表达式和方法,是把表达式转换为对应的Sql语句来执行,解析到nameFilter的时候他就懵逼了,这是啥玩意儿?。瑂ql里面没有这种东西啊,他就转换不了了 。
小伙子知道后明细很失望,那不能?。膊皇俏蚁胂园谖业募际? ,就是想让小伙子能继续他的骚操作,给他来点海克斯科技与狠活 。
解决方案:
//表达式Func<Report,bool> nameFilter = x=>x.Name == "test";Func<Report,bool> stateFilter = x=>x.State==1;//合并为Func<Report,bool> whereFilter = x=>x.Name == "test" && x.State==1;//调用DbContext.Report.Where(whereFilter);
完美解决
那怎么合并,当然得自己构造一个新的表达式,构造表达式需要用到Expression
类 , 如果没有用过这个类 , 可以按照下面的方式来调试看看一个表达式转换为表达式树是怎么样的 。
TestExpression(x=>x.Name == "test",x=>x.State==1);public static void TestExpression(Expression<Func<Report, bool>> left,Expression<Func<Report, bool>> right){//调试查看expression对象var bodyLeft = left.Body;//这个就是x.Name == "test"var bodyRight = right.Body;//这个就是x.State==1}
好 , 这里我们能获取到表达式的Body,然后使用Expression
类能很好的合并两个表达式的body
var andAlso = Expression.AndAlso(bodyLeft ,bodyRight);//x.Name == "test" && x.State==1
这样还不行,这两个表达式是两个不同的委托对象,他们的参数x也是两个不同的对象,合并了又没完全合并
这就需要用到ExpressionVisitor
类来递归表达式树 , 把两个表达式的参数替换为同一个参数 。
/// <summary>/// 替换表达式参数/// </summary>public class ReplaceExpressionVisitor : ExpressionVisitor{private Expression _leftParameter;public ReplaceExpressionVisitor(Expression leftParameter){_leftParameter= leftParameter;}protected override Expression VisitParameter(ParameterExpression node){return _leftParameter;}}
最终
TestExpression(x=>x.Name == "test",x=>x.State==1);public static void TestExpression(Expression<Func<Report, bool>> left,Expression<Func<Report, bool>> right){//调试查看expression对象var bodyLeft = left.Body;//这个就是x.Name == "test"var bodyRight = right.Body;//这个就是x.State==1var leftParameter = left.Parameters[0];//表达式递归访问var visitor =new ReplaceExpressionVisitor(leftParameter);//替换参数bodyRight = visitor.Visit(bodyRight);//合并表达式var expression = Expression.AndAlso(bodyLeft , bodyRight);//构建表达式var whereExpression= Expression.Lambda<Func<Report, bool>>(expression , left.Parameters);//编译表达式var whereFilter = whereExpression.Compile();//使用DbContext.Report.Where(whereFilter);}
正想给小老弟显摆一下的时候,他又去写其他骚代码了
骚不过骚不过 , 完善一下列子,下面是完整的代码
小嫩手不想动的小伙伴可以直接nuget上查找DynamicExpression.Core
,直接使用
更多源码看本人github
/// <summary>/// 替换表达式参数/// </summary>public class ReplaceExpressionVisitor : ExpressionVisitor{private Dictionary<Expression, Expression> _parameters;public ReplaceExpressionVisitor(Dictionary<Expression,Expression> parameters){_parameters = parameters;}protected override Expression VisitParameter(ParameterExpression node){if (_parameters.TryGetValue(node, out Expression _newValue)){return _newValue;}return base.Visit(node);}}
/// <summary>/// 表达式扩展/// </summary>public static class ExpressionExtension{/// <summary>/// 使用AndAlso合并表达式/// </summary>/// <param name="exprs"></param>/// <returns></returns>public static Expression<T> AndAlso<T>(this IList<Expression<T>> exprs){if (exprs.Count == 0) return null;if (exprs.Count == 1) return exprs[0];var leftExpr = exprs[0];var left = leftExpr.Body;for (int i = 1; i < exprs.Count; i++){var expr = exprs[i];var visitor = GetReplaceExpressionVisitor(expr.Parameters, leftExpr.Parameters);var right = visitor.Visit(expr.Body);left = Expression.AndAlso(left, right);}return Expression.Lambda<T>(left, leftExpr.Parameters);}/// <summary>/// 使用AndAlso合并表达式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="left"></param>/// <param name="right"></param>/// <returns>left AndAlso right</returns>public static Expression<T> AndAlso<T>(this Expression<T> left, Expression<T> right){return AndAlso(new List<Expression<T>>() { left, right });}/// <summary>/// 使用OrElse合并表达式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="exprs"></param>/// <returns></returns>public static Expression<T> OrElse<T>(this IList<Expression<T>> exprs){if (exprs.Count == 0) return null;if (exprs.Count == 1) return exprs[0];var leftExpr = exprs[0];var left = leftExpr.Body;for (int i = 1; i < exprs.Count; i++){var expr = exprs[i];var visitor = GetReplaceExpressionVisitor(expr.Parameters, leftExpr.Parameters);var right = visitor.Visit(expr.Body);left = Expression.OrElse(left, right);}return Expression.Lambda<T>(left, leftExpr.Parameters);}/// <summary>/// 使用OrElse合并表达式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="left"></param>/// <param name="right"></param>/// <returns>left OrElse right</returns>public static Expression<T> OrElse<T>(this Expression<T> left, Expression<T> right){return OrElse(new List<Expression<T>>() { left, right });}/// <summary>/// 构建visitor/// </summary>/// <param name="oldParameters"></param>/// <param name="newParameters"></param>/// <returns></returns>private static ReplaceExpressionVisitor GetReplaceExpressionVisitor(ReadOnlyCollection<ParameterExpression> oldParameters, ReadOnlyCollection<ParameterExpression> newParameters){Dictionary<Expression, Expression> dic = new Dictionary<Expression, Expression>();for (int i = 0; i < oldParameters.Count; i++){dic.Add(oldParameters[i],newParameters[i]);}return new ReplaceExpressionVisitor(dic);}}
推荐阅读
- .NET Core C#系列之XiaoFeng.Threading.JobScheduler作业调度
- SpringBoot框架SpEL表达式注入漏洞复现与原理分析
- 【番外篇】Rust环境搭建+基础开发入门+Rust与.NET6、C++的基础运算性能比较
- .NET周报【10月第2期 2022-10-17】
- 聊一聊被 .NET程序员 遗忘的 COM 组件
- 细聊.Net Core中IServiceScope的工作方式
- asp.net core web 解决方案多项目模板制作打包总结
- ubuntu-22.04 树莓派Zero 2 W通过.NET6和libusb操作USB读写
- 上 学习ASP.NET Core Blazor编程系列六——新增图书
- Azure DevOps Pipelines部署.Net Core 应用到Kubernetes