我有一个包含多个字段的表单(公司名称,邮政编码等),允许用户搜索数据库中的公司。如果用户在多个字段中输入值,那么我需要搜索所有这些字段。我正在使用LINQ来查询数据库。
到目前为止,我已经设法编写了一个函数,它将查看它们的输入并将其转换为表达式列表。我现在想将该List转换为单个表达式,然后我可以通过LINQ提供程序执行。
我最初的尝试如下
private Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions)
{
if (expressions.Count == 0)
{
return null;
}
if (expressions.Count == 1)
{
return expressions[0];
}
Expression<Func<Company, bool>> combined = expressions[0];
expressions.Skip(1).ToList().ForEach(expr => combined = Expression.And(combined, expr));
return combined;
}
然而,这会失败,并出现“二元运算符并且未定义...”的异常消息。有没有人有任何想法,我需要做什么来结合这些表达式?
编辑:纠正了我忘记将结果和表达式一起分配给变量的行。谢谢你指出那些人。
答案 0 :(得分:23)
您可以将Enumerable.Aggregate
与Expression.AndAlso
结合使用。这是一个通用版本:
Expression<Func<T, bool>> AndAll<T>(
IEnumerable<Expression<Func<T, bool>>> expressions) {
if(expressions == null) {
throw new ArgumentNullException("expressions");
}
if(expressions.Count() == 0) {
return t => true;
}
Type delegateType = typeof(Func<,>)
.GetGenericTypeDefinition()
.MakeGenericType(new[] {
typeof(T),
typeof(bool)
}
);
var combined = expressions
.Cast<Expression>()
.Aggregate((e1, e2) => Expression.AndAlso(e1, e2));
return (Expression<Func<T,bool>>)Expression.Lambda(delegateType, combined);
}
您当前的代码永远不会分配给combined
:
expr => Expression.And(combined, expr);
返回一个新的Expression
,它是按位combined
和expr
的结果,但它不会改变combined
。
答案 1 :(得分:9)
我假设您正在将这些用于Where
子句...为什么不依次调用每个表达式的Where?这应该具有相同的效果:
var query = ...;
foreach (var condition in conditions)
{
query = query.Where(condition);
}
答案 2 :(得分:0)
这里我们有一个关于组合Linq表达式的一般问题。我有一个解决这个问题的通用解决方案。我将就所发布的具体问题提供答案,尽管在这种情况下绝对不是这样的。但是,如果简单的解决方案在您的情况下失败,您可以尝试使用此方法。
首先,您需要一个由2个简单函数组成的库。他们使用System.Linq.Expressions.ExpressionVisitor
动态修改表达式。关键特性是统一表达式中的参数,以使具有相同名称的2个参数相同(UnifyParametersByName
)。剩下的部分是用给定的表达式(ReplacePar
)替换命名参数。该库可以在github上使用MIT许可证:LinqExprHelper,但你可以自己写一些东西。
该库允许使用非常简单的语法来组合复杂表达式。你可以混合使用的内联lambda表达式,以及非常强大的动态表达式创建和组合。
private static Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions)
{
if (expressions.Count == 0)
{
return null;
}
// Prepare a master expression, used to combine other
// expressions. It needs more input parameters, they will
// be reduced later.
// There is a small inconvenience here: you have to use
// the same name "c" for the parameter in your input
// expressions. But it may be all done in a smarter way.
Expression <Func<Company, bool, bool, bool>> combiningExpr =
(c, expr1, expr2) => expr1 && expr2;
LambdaExpression combined = expressions[0];
foreach (var expr in expressions.Skip(1))
{
// ReplacePar comes from the library, it's an extension
// requiring `using LinqExprHelper`.
combined = combiningExpr
.ReplacePar("expr1", combined.Body)
.ReplacePar("expr2", expr.Body);
}
return (Expression<Func<Company, bool>>)combined;
}