Say I have a repository which allows my to pass custom expressions to filter on the main object, along with another custom expression that allows me to filter the related objects.
DbContext entities expect an Expression<Func<>> to filter.
One-to-many Navigation properties are generally ICollections<>, and can only have the .Include clause filtered with Func<> predicates.
It seems strange to me that main entity requires an Expression, but the navigation property can only be filtered with a Func<>.
For example, this won't compile, specifically .Include(c => c.Orders.Where(orderCriteria)), because orderCriteria is an expression:
public class Customer {
public int Id { get; set; }
public virtual ICollection<Order> Orders { get; set;}
}
public class Order {
public int Id { get; set; }
public int CustomerId { get; set;}
public Customer Customer { get; set; }
}
public class CustomerRepository
{
private DbContext _dbContext = new DbContext();
List<Customer> SearchCustomers(
Expression<Func<Customer, bool>> customerCriteria,
Expression<Func<Order, bool>> orderCriteria) {
return _dbContext.Customers.Where(customerCriteria)
.Include(c => c.Orders.Where(orderCriteria)).ToList(); // won't compile
}
}
Should I just call .Include(c => c.Orders.Where(orderCriteria.Compile())) ? Or just pass orderCriteria as a Func<Order,bool> rather than as an expression?
And will the query on the Order entities still be performed at the database level in SQL?
None of these options will work. Notice that
Includeitself accepts anExpression. EF will analyze this expression, including part insideWhere. For example consider this:Expression inside
Includecan be analyzed completely, including logic insideWherecall. It's not relevant thatWhereacceptsFuncand not expression because thisWherecall is itself part of expression.Now if you have this:
Now we also have expression inside
Include. However, this expression contains call toCompilewhich is unknown to EF and cannot be translated to SQL, so it will fail. In theory, EF could extract value from this expression, because the necessary info is available there insideorderCriteria, but it doesn't do that.You could try something like:
This will not work - now necessary info is not available at all in expression tree, so there is no way to extract original meaning of
Amount >= 1000from this.To solve this you can use LinqKit or similar libraries. Note how I said before that with this approach:
necessary information is in theory there and can be extracted, but EF doesn't do this. Well, LinqKit extracts this information and basically rewrites your expression in "expected" form. With this library you need to use special marker method
AsExpandable:Using this
AsExpandablemethod will allow LinqKit to analyze the following query and replace the usage ofExpression.Compilewith the contents of actual expression to make EF happy.If you don't want to take dependency on third party library - it's open source so you can check how they do it (it's not too complicated), and do it yourself.