2

Appologies if this is a simple question; suppose I have an EF Query object with 2 methods:

public static IQueryable<Animal> FourLegged(this IQueryable<Animal> query)
{
    return query.Where(r => r.NumberOfLegs == 4);
}

public static IQueryable<Animal> WithoutTail(this IQueryable<Animal> query)
{
    return query.Where(r => !r.HasTail);
}

Now, in my service layer, to get animals that are four-legged AND without a tail, I can do:

_animalService.Query()
.FourLegged()
.WithoutTail();

That will result in an sql query like so:

select * from Animal where NumberOfLegs = 4 AND HasTail = 0

How do I use the 2 query methods with an OR instead? I want animals that are either 4 legged OR without a tail

select * from Animal where NumberOfLegs = 4 OR HasTail = 0

In Nhibernate I would have used a simple disjunction, but I can't find that in EF.

Thanks


Solution: I ended up using LinqKit predicates mentioned on this answer. It works quite well and I can reuse predicates too.

Community
  • 1
  • 1
LocustHorde
  • 6,361
  • 16
  • 65
  • 94
  • 2
    It is probably already too late there. But if you have the `Expression<>` (`r => r.NumberOfLegs == 4` and `r => !r.HasTail` without the `.Where()`) you can use [PredicateBuilder](http://www.albahari.com/nutshell/predicatebuilder.aspx) If you search in stackoverflow for PredicateBuilder there are various questions. – xanatos Feb 26 '16 at 10:06
  • 1
    I used a custom Expression composition to solve this kind of need. It's very cumbersome, I'll post an answer when I have the time (if someone else does not arrive before) – edc65 Feb 26 '16 at 10:10
  • @xanatos - excellent, predicate builder works fine, would you post that as an answer please, so I can accept? thanks – LocustHorde Feb 26 '16 at 10:17
  • @edc65 maybe you can post your answer as well, when you get around to it, it would be interesting to compare, thanks – LocustHorde Feb 26 '16 at 10:18

1 Answers1

2

You can’t really do this when you already called query.Where(). The predicates there are already collected in the IQueryable and they are all combined by AND.

In order to get an OR you will have to make a single query.Where() call and pass a single expression that covers your various disjunctive predicates.

In your case, the combined predicate would look like this:

query.Where(r => (r.NumberOfLegs == 4) || (!r.HasTail))

To make that more dynamic, you essentially need to build a custom expression composition function that works like this:

Expression<Func<Animal, bool>> fourLegged = r => r.NumberOfLegs == 4;
Expression<Func<Animal, bool>> withoutTail = r => !r.HasTail;

query = query.Where(CombineDisjunctivePredicates(fourLegged, withoutTail));

So let’s write that CombineDisjunctivePredicates function:

public Expression<Func<T, bool>> CombineDisjunctivePredicates<T>(params Expression<Func<T, bool>>[] predicates)
{
    Expression current = Expression.Constant(false);
    ParameterExpression param = Expression.Parameter(typeof(T), "obj");

    foreach (var predicate in predicates)
    {
        var visitor = new ReplaceExpressionVisitor(predicate.Parameters[0], param);
        current = Expression.Or(current, visitor.Visit(predicate.Body));
    }

    return Expression.Lambda<Func<T, bool>>(current, param);
}

This basically takes a number of predicates and combines them by combining the expression bodies using the boolean OR. Since we are combining different expressions which may have different expression parameters, we also need to make sure to replace all expression parameter references in the expression bodies using a common parameter. We do this using a simple ReplaceExpressionVisitor, easily implemented like this:

public class ReplaceExpressionVisitor : ExpressionVisitor
{
    private readonly Expression _original;
    private readonly Expression _replacement;

    public ReplaceExpressionVisitor(Expression original, Expression replacement)
    {
        _original = original;
        _replacement = replacement;
    }

    public override Expression Visit(Expression node)
    {
        return node == _original ? _replacement : base.Visit(node);
    }
}

And that’s all you need to combine the predicates. You just need to make sure to change your methods now so they don’t call query.Where themselves but return a Expression<Func<Animal, bool>> instead.

poke
  • 369,085
  • 72
  • 557
  • 602