entity-framework-coreexpression-treespredicatebuilder

How to pass a runtime parameter to an EF Core predicate expression


In an app the uses EF Core, I am attempting to eliminate duplication of query code by creating a reusable library of predicate expressions. However, I am struggling with predicates that accept runtime parameters.

Let's assume 2 simple entity classes in a parent child relationship:

public class Parent
{
    public double Salary { get; set; }
    public ICollection<Child> Children { get; set; }

}

public class Child
{
    public double Salary { get; set; }
}

I can retrieve all the parents with a child that earns more than them, using a conventional EF Core query like this:

return Context.Set<Parent>()
    .Where(parent => parent.Children.Any(child =>
        child.Salary > parent.Salary));

If I want to create a reusable predicate for the above query, I imagine it might look something like this:

private Expression<Func<Child, Parent, bool>> EarnsMoreThanParent = (Child child, Parent parent) => child.Salary > parent.Salary;

The above predicate compiles OK, but I haven't found any way to consume it. The problem is how to pass the parent entity into it at runtime. This doesn't compile:

return _context.Set<Parent>()
    .Where(parent => parent.Children.Any(child =>
        EarnsMoreThanParent(child, parent));

I understand that the I am conflating the Expression<> and Func<> syntax, but I assume that this is a relatively common requirement that must be possible.

Thanks


Solution

  • You can try to wrap the whole expression like this

    private Expression<Func<Parent, bool>> EarnsMoreThanParent = 
        (Parent parent) => parent.Children.Any(child => child.Salary > parent.Salary)
    

    and then use for the whole Where

    return _context.Set<Parent>().Where(EarnsMoreThanParent);
    

    The problem with your code is that in .Where(parent => parent.Children.Any(child => EarnsMoreThanParent(child, parent)) the whole expression inside is an expression. So your method EarnsMoreThanParent won't be called as it's part of Where expression and EF will try to parse it as expression and would fail.

    But yeah, if you need to combine a couple of such condition with different OR it might not work as it would be like Where(EanrsMoreThanParent).Where(SomeOtherCondition) and all of them will be translated to AND