0

Say I have two classes:

public class Parent {
   public Child Child { get; set; }
}

public class Child {
   public string Name { get; set; }
}

And I want to write a function that filters Parents by their child's name containing a substring.

public IQueryable<Parent> ParentsChildNameMatches(IQueryable<Parent> parents, string word)
{
   return parents.Where(parent => parent.Child.Name.Contains(word));
}

If I then want to pull out that Expression so I can reuse the Child contains check elsewhere

public Expression<Func<Child, bool> ChildNameMatches(string word)
{
   return child => child.Name.Contains(word));
}

How do I then use that in my .Where such that I can rewrite ParentsChildNameMatches?

public IQueryable<Parent> ParentsChildNameMatches(IQueryable<Parent> parents, string word)
{
   return parents.Where( // how to leverage ChildNameMatches?
}
Michael Wilson
  • 1,855
  • 2
  • 12
  • 16
  • 1
    Actually, shouldn't your `ChildNameMatches` accept `Parent`, not `Child`, and be `return parent => parent.Child. ...`? – Patrick Roberts Jul 17 '18 at 15:13
  • Well, my goal here is that I can then reuse the ChildNameMatches expression for either Child classes by themselves, or when there are other parent classes with Child properties. – Michael Wilson Jul 17 '18 at 15:18
  • If that particular method `ChildNameMatches` is defined in the `Child` class, then you can add another one with the signature I suggested for the `Parent` class, otherwise you won't be able to directly use it in that `.Where()` invocation. – Patrick Roberts Jul 17 '18 at 15:24

2 Answers2

2

So, given an Expression<Func<Child, bool>>, and the fact that you can create an expression from Parent to Child via the Child property in the Parent class, you want to obtain a Expression<Func<Parent, bool>>.

You can do this manually using the expression trees API, or you can use some 3rd party libraries like LinqKit like this:

public IQueryable<Parent> ParentsChildNameMatches(IQueryable<Parent> parents, string word)
{
    var childExpression = ChildNameMatches(word);

    Expression<Func<Parent, bool>> expression = p => childExpression.Invoke(p.Child);

    expression = expression.Expand();

    return parents.Where(expression);
}

LinqKit provides the Invoke method that allows you to compose expressions together. It also provides the Expand method which flattens the resultant expression.

You can read more about this here: http://www.albahari.com/nutshell/linqkit.aspx

UPDATED: previous code did not work. Fixed.

Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
1

You can combine your lambdas with Invoke

public static IQueryable<Parent> ParentsChildNameMatches(IQueryable<Parent> parents, string word)
{
    var childPredicate = ChildNameMatches(word);

    var parent = Expression.Parameter(typeof(Parent), "parent");
    var parentPredicate = Expression.Lambda<Func<Parent, bool>>(
        Expression.Invoke(
            childPredicate, 
            Expression.Property(parent, "Child")),
        parent);

    return parents.Where(parentPredicate);
}

This code will create a new expression parent => parent.Child and use it as paremeter to childPredicate

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37