如何在Linq中创建一个复合的“或”条件?

8
如果您想在Linq查询中添加“and”条件,可以按照以下方式轻松完成:
var q = MyTable;
if (condition1)
  q = q.Where(t => t.Field1 == value1);
if (condition2)
  q = q.Where(t => t.Field2 > t.Field3);
// etc.

当您想要添加“或”条件时,有没有聪明的方法可以做到相同的事情?


它涉及表达式树的合并,以及(如果您正在使用EF)重写...它不一定很漂亮...但是如果您真的需要,可以完成... - Marc Gravell
@Marc Gravell - 嗯...你说的"不美观"是指什么?我需要将"或"子句组合起来,但如果要做出"优雅"的解决方案比通过创建一个独立的联合查询来完成更费力,那可能就不值得了。这个任务有多难? - Shaul Behr
1
请查看此答案:https://dev59.com/smw15IYBdhLWcg3w0e8V#6383937 - Eranga
嘿@Eranga,为什么不在这里发布那种解决方案作为答案呢? - Shaul Behr
提供另一种解决方案,以便您可以选择喜欢的方法。 - Eranga
5个回答

10
您可以使用PredicateBuilder来构建基于Or的表达式:
 var predicate = PredicateBuilder.False<Product>();

 predicate = predicate.Or (t => t.Field1 == value1);
 predicate = predicate.Or (t => t.Field2 > t.Field3);

 q = q.Where (predicate);

你可以在这里了解更多信息: http://www.albahari.com/nutshell/predicatebuilder.aspxPredicateBuilder.False<Product>()中的Product替换为你查询的对象。
请注意,由于要使用Or,所以从False谓词开始。 如果你想要一个And谓词,你应该从True开始。

4
请使用以下内容:
var q = MyTable;
q = q.Where(
     t => (condition1 && t.Field1 == value1) || (condition2 && t.Field2 > t.Field3));

2
感谢提供帮助!很高兴有一些善意越过我们的国际边界...;) - Shaul Behr

2

有一种方法可以使用表达式树来实现。这种方法是自己构建布尔表达式。它相当简单,但棘手的部分是你需要重新调整参数,否则它将引用原始的Lambda表达式。以下是一个示例:

static void Main(string[] args)
{
    var source = new List<int> { 1, 2, 3 };

    var any = new List<Expression<Func<int, bool>>>();

    any.Add(x => x == 1);
    any.Add(x => x == 3);

    foreach (var item in source.AsQueryable().WhereDisjunction(any))
    {
        Console.WriteLine(item);
    }
}

class RewriteSingleParameterUsage : ExpressionVisitor
{
    public ParameterExpression Parameter { get; set; }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return Parameter;
    }
}

public static IQueryable<T> WhereDisjunction<T>(this IQueryable<T> source, IList<Expression<Func<T, bool>>> any)
{
    switch (any.Count)
    {
        case 0: return source;
        case 1: return source.Where(any[0]);
        default:
            var p = Expression.Parameter(any[0].Parameters[0].Type, any[0].Parameters[0].Name);
            var rw = new RewriteSingleParameterUsage { Parameter = p };
            var expr = rw.Visit(any[0].Body);
            for (int i = 1; i < any.Count; i++)
            {
                expr = Expression.Or(expr, rw.Visit(any[i].Body));
            }
            return source.Where(Expression.Lambda<Func<T, bool>>(expr, p));
    }
}

在上面的例子中,我非常严格,实际上是用这个新参数替换任何参数来创建新表达式。然而,考虑到这个扩展方法的签名,不应该使用参数调用此方法,以避免发生错误。但是,如果涉及多个参数,则会出现问题。

你在布尔逻辑中犯了一个错误。你是想在condition2后面加上||吗?即使这样,它看起来也不对:如果condition1condition2都为真,则你已经在两个其他表达式之间创建了一个“and”条件。@Akram的布尔表达式才是正确的... - Shaul Behr
我之前警告过你这很不直观。现在我发现我犯了一个错误,没关系。 - John Leidegren
我会给你加一分,因为你很努力了,但我认为PredicateBuilder是这里提供的最好、最简单的解决方案。无论如何,还是谢谢你! - Shaul Behr
@Shaul - 虽然有一个为你完成任务的库有时很方便,但我认为了解这些操作的工作原理也是必要的。PredicateBuilder 不仅可以为您创建复合析取式,还需要额外的操作,例如 AsExpandable。这些操作并不是免费的。 - John Leidegren

2
var q = MyTable;
var conditions = new List<Func<T, bool>>();

 if (condition1)
     conditions.Add(t => ...);
 if (condition2)
     conditions.Add(t => ...);

 q.Where(x => conditions.Any(y => y(x)));

好吧,这并不是真正的LINQ,因为它不基于IQueryable或表达式树,所以无法转换到其他执行环境。 - John Leidegren
是的,没错,它无法转换为SQL,这使得它对我的目的来说相当低效。 - Shaul Behr

1

这是我这里给出的相同答案。

正如Marc Gravell所说,它涉及表达式树的组合。

这个文章向你展示了如何做到这一点。最初设置需要一些工作,但它是值得的。

另一种解决方案是使用Predicate Builder。该文章没有很好地解释底层实际发生了什么。但上面的文章很好地解释了它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接