4

I'm trying to construct an IQueryable which will be evaluated by my entity model. I want to pass it two groups of lambdas and have it compose everything into a more complex expression tree, which is passed on to the database for execution.

Here's what I have so far:

public class FilterManager<T>
{
    public List<Expression<Func<T, bool>>> Inclusive { get; set; }
    public List<Expression<Func<T, bool>>> Exclusive { get; set; }

    public IQueryable<T> ApplyFilters(IQueryable<T> query)
    {
        var q = query;

        Exclusive.ForEach(exp => q = q.Where(exp)); //works fine
        Inclusive.ForEach(exp => /* ??? */); 

        return q;
    }

    //ctor, etc.
}

The idea here is that I add several Expressions to Inclusive, which "Ors" them together. For example, if T is an int, the code:

fm.Inclusive.Add(x => x > 1);
fm.Inclusive.Add(y => y < 5);

query = fm.ApplyFilters(query);

should have the same result set as:

query = query.Where(z => z > 1 || z < 5);

How can I get Inclusive to work without third-party tools such as PredicateBuilder? Third-party tools are usually fine, but I'd like to improve my understanding of how to compose expressions in .NET.

I also need to make sure that the tree won't be evaluated yet, so that I can do the filtering on the database. That means I'll need to produce something Entity Framework 4.0 can consume.

Justin Morgan - On strike
  • 30,035
  • 12
  • 80
  • 104
  • 1
    Dup: http://stackoverflow.com/questions/5430996/replacing-the-parameter-name-in-the-body-of-an-expression ? (my answer there shows 2 ways of inlining the expressions; the "visitor" version is preferred, IMO) – Marc Gravell May 25 '11 at 21:34
  • @Marc - Thanks, I'm reading your link now. Man, that's a lot of code when you drill into ExpressionRewriter. But does this apply to an `OrElse` type of behavior? The question there is about `AndAlso`, which I think is more in line with my `Exclusive` set. – Justin Morgan - On strike May 25 '11 at 21:46
  • 2
    One important thing to remember is that in your example, the "x" in each of x=>x>1 and x=>x<5 are *different* variables. Imagine they were called x1 and x2. If you naively combine them together into a third then you get x3=>x1>1||x2<5, which obviously makes no sense. The expression tree code makes parameter references equal *by reference*, and not *by name*. The names are essentially ignored. – Eric Lippert May 25 '11 at 21:54
  • @Eric - Yes, sorry if I was unclear. What I meant was that those two snippets should return the same result set, without necessarily having the same internal behavior, or using the same variables. I've renamed the variables to make that a little more clear. – Justin Morgan - On strike May 25 '11 at 22:01
  • @Marc - Thanks for the link, I think your visitor code is definitely the right way of doing this. If this simple version can't hack it I'll be using your code. – Justin Morgan - On strike May 25 '11 at 22:29

4 Answers4

2

The closest match I can think of is this:

public IQueryable<T> ApplyFilters(IQueryable<T> query)
{
    IQueryable<T> q;

    if (!Inclusive.Any())
        q = query;
    else
    {
        q = Enumerable.Empty<T>();
        Inclusive.ForEach(exp => q = q.Union(query.Where(exp)));
    }

    Exclusive.ForEach(exp => q = q.Where(exp));

    return q;
}

But I'm almost sure that this will be very inefficient

Snowbear
  • 16,924
  • 3
  • 43
  • 67
  • 1
    No, it can be done more cleanly by using a visitor to flatten the expressions – Marc Gravell May 25 '11 at 21:36
  • I've been trying some similar things to this. Unfortunately, this will cause the wrong interaction between `Exclusive` and `Inclusive`, since stuff that matches `Inclusive` but not `Exclusive` will get added in. (Hope that makes sense.) – Justin Morgan - On strike May 25 '11 at 21:39
  • @Justin, then you should do it in reverse order - first add `Inclusive` filters, then `Exclusive`. See update. But you should definitely look into *Marc*'s code – Snowbear May 25 '11 at 21:44
  • This seems to work. The only change I made was putting the `Exclusive.ForEach` call at the beginning of the method. I'll add an answer with the exact code, but your answer is what solved this for me. Thanks. – Justin Morgan - On strike May 25 '11 at 22:20
0

Try something like this? I'm not sure I haven't tested it.

Inclusive.ForEach(exp => q = q.Union(q.Where(exp)));
Jay
  • 6,224
  • 4
  • 20
  • 23
0

Even though there's already an accepted answer, I would like to point out you can use predicate builder to combine the expressions with an Or. This will keep it as a simple query to the database.

http://www.albahari.com/nutshell/predicatebuilder.aspx

Doggett
  • 3,364
  • 21
  • 17
0

I haven't tested it on my entity model yet, so I don't know if it will be supported by EF, but the following works for L2O. It's just a slight change from Snowbear JIM-compiler's code:

public IQueryable<T> ApplyFilters(IQueryable<T> query)
{
    Exclusive.ForEach(exp => query = query.Where(exp));

    if (Inclusive.Count == 0)
    {
        return query;
    }

    IQueryable<T> q = Enumerable.Empty<T>().AsQueryable<T>();
    Inclusive.ForEach(exp => q = q.Union(query.Where(exp)));

    return q;
}
Justin Morgan - On strike
  • 30,035
  • 12
  • 80
  • 104