How to use System.Linq.Expressions.Expression to filter based on children?

24,520

Solution 1

If you want to combine expressions and still be able to use linq-to-sql, you may want to have a look at LinqKit. It walks inside your expression and replaces all the function calls by their contents before the sql conversion.

This way you'll be able to use directly

return db.Parents
       .AsExpandable()
       .Where(parent => parent.Status == 1 && filter(parent.Child));

Solution 2

You can try this:

var compiledFilter = filter.Compile();
foreach (var parent in db.Parents.Where(parent => parent.Status == 1))
    if (compiledFilter(parent.Child))
        yield return parent;

It requires you to pull all of the parents, but unlike @HugoRune's solution, it doesn't require a 1:1 relation of Parent:Child.

I don't think this will be useful for your situation because of the different types involved, but just in case, here is an example of how you can combine Expressions: How do I combine LINQ expressions into one?

Edit: I had previously suggested using Compile(), but that doesn't work over LINQ-to-SQL.

Solution 3

Well, if there is a 1:1 relationship between parent and child (unlikely, but the example seems to imply that) then you could do it like this:

  return db.Parents
  .Where(parent => parent.Status == 1)
  .Select(parent => parent.Child)
  .Where(filter)
  .Select(child=> child.Parent);

Otherwise it will be hard.

You could do it with dynamic linq but that is probably overkill.

You could generate your expression tree manually, but that is also quite complicated. I have not tried that myself.

As a last resort you could of course always call yourQuery.AsEnumerable(), this will cause linq-to-sql to translate your query into sql up to this point and perform the rest of the work on the client-side; then you can .compile() your expression. However you lose the performance benefits of linq-to-sql (and compile() itself is quite slow; whenever it is executed, it calls the JIT-compiler):

  return db.Parents
  .Where(parent => parent.Status == 1)
  .AsEnumerable()
  .Where(parent  => filter.Compile().Invoke(parent.Child))

Personally I'd just define the expression twice, once for child and once for parent.child:

   Expression<Func<Child, bool>> filterChild = child => child.Status == 1;
   Expression<Func<Parent, bool>> filterParent = parent => parent.Child.Status == 1;

Might not be the most elegant, but probably easier to maintain than the other solutions

Share:
24,520
Jader Dias
Author by

Jader Dias

Perl, Javascript, C#, Go, Matlab and Python Developer

Updated on July 09, 2022

Comments

  • Jader Dias
    Jader Dias almost 2 years

    I have a filter that I use across many methods:

    Expression<Func<Child, bool>> filter = child => child.Status == 1;
    

    (actually is more complex than that)

    And I have to do the following

    return db.Parents.Where(parent => parent.Status == 1 &&
                                      parent.Child.Status == 1);
    

    where the condition is the same as in the filter above.

    I want to reuse the filter in this method. But I don't know how. I tried

    return db.Parents.Where(parent => parent.Status == 1 &&
                                      filter(parent.Child));
    

    but an Expression can't be used as a method