如何将ViewModel转换为Expression<Func<T,bool>>并组合多条件表达式?
如何组合多个
Expression<Func<Store, bool>>为复合查询谓词? 我现在在做一个从ViewModel生成查询谓词的功能,目的是给IQueryable.Where用,根据用户提供的参数来动态包含或排除查询条件。我已经写了部分代码,但卡在了如何把多个表达式组合成一个复合表达式的步骤上。示例代码如下:
public class StoresFilter { public int[] Ids { get; set; } [StringLength(150)] public string Name { get; set; } [StringLength(5)] public string Abbreviation { get; set; } [Display(Name = "Show all")] public bool ShowAll { get; set; } = true; public Expression<Func<Store, bool>> ToExpression() { List<Expression<Func<Store, bool>>> expressions = new List<Expression<Func<Store, bool>>>(); if (Ids != null && Ids.Length > 0) { expressions.Add(x => Ids.Contains(x.Id)); } if (Name.HasValue()) { expressions.Add(x => x.Name.Contains(Name)); } if (Abbreviation.HasValue()) { expressions.Add(x => x.Abbreviation.Contains(Abbreviation)); } if (!ShowAll) { expressions.Add(x => x.Enabled == true); } if (expressions.Count == 0) { return x => true; } // 这里不知道怎么把多个表达式组合成一个复合表达式??? return compositeExpression; } }
想请教一下,有没有简便的方法能把这些表达式组合起来,还是说必须手动用ParameterExpression、Expression.AndAlso、ExpressionVisitor这些类来构建?
嘿,这个问题我之前也碰到过,其实不用完全手动从零开始构建表达式树,有两种比较省心的解决方式:
1. 自己实现一个轻量的表达式组合扩展方法
你可以写一个简单的扩展方法,专门用来把多个Expression<Func<T, bool>>用逻辑与(AndAlso)拼接起来。核心是用ExpressionVisitor替换每个表达式里的参数,让它们都指向同一个参数实例,这样就能安全拼接了。
比如可以这样写:
public static class ExpressionExtensions { public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) { // 创建一个统一的参数 var sharedParam = Expression.Parameter(typeof(T)); // 用访问器替换两个表达式里的参数为统一参数 var paramReplacer = new ParameterReplaceVisitor(right.Parameters[0], sharedParam); var leftBody = paramReplacer.Visit(left.Body); var rightBody = paramReplacer.Visit(right.Body); // 拼接逻辑与 var combinedBody = Expression.AndAlso(leftBody, rightBody); return Expression.Lambda<Func<T, bool>>(combinedBody, sharedParam); } private class ParameterReplaceVisitor : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly ParameterExpression _newParam; public ParameterReplaceVisitor(ParameterExpression oldParam, ParameterExpression newParam) { _oldParam = oldParam; _newParam = newParam; } protected override Expression VisitParameter(ParameterExpression node) { // 替换目标参数 return node == _oldParam ? _newParam : base.VisitParameter(node); } } }
然后回到你的ToExpression方法里,只需要用LINQ的Aggregate方法就能一键组合所有表达式:
return expressions.Aggregate((currentExpr, nextExpr) => currentExpr.AndAlso(nextExpr));
2. 用现成的工具库省事儿
如果不想自己写代码,也可以用LINQKit这类专门处理LINQ表达式的库,它已经封装好了表达式组合的逻辑,还能帮你处理EF Core解析的问题。
用LINQKit的话,你的代码可以改成这样:
// 初始化一个默认返回true的谓词 var predicate = PredicateBuilder.New<Store>(true); foreach (var expr in expressions) { // 逐个用And拼接表达式 predicate = predicate.And(expr); } return predicate;
要是用EF Core的话,记得在你的IQueryable前面加上AsExpandable(),这样LINQKit的表达式才能被EF正确转换成SQL语句。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

