Looking at the code, one will assume that the type of castCondition
variable is Expression<Func<CastInfo, bool>>
(as it was in earlier versions of PredicateBuilder
).
But if that was the case, then n.CastInfo.Any(castCondition)
should not even compile (assuming CastInfo
is a collection navigation property, so the compiler will hit Enumerable.Any
which expects Func<CastInfo, bool>
, not Expression<Func<CastInfo, bool>>
). So what's going on here?
In my opinion, this is a good example of C# implicit operator abuse. The PredicateBuilder.New<T>
method actually returns a class called ExpressionStarter<T>
, which has many methods emulating Expression
, but more importantly, has implicit conversion to Expression<Func<T, bool>>
and Func<CastInfo, bool>
. The later allows that class to be used for top level Enumerable
/ Queryable
methods as replacement of the respective lambda func/expression. However, it also prevents the compile time error when used inside the expression tree as in your case - the complier emits something like n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
which of course causes exception at runtime.
The whole idea of LinqKit AsExpandable
method is to allow "invoking" expressions via custom Invoke
extension method, which then is "expanded" in the expression tree. So back at the beginning, if the variable type was Expression<Func<CastInfo, bool>>
, the intended usage is:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
But now this doesn't compile because of the reason explained earlier. So you have to convert it first to Expression<Func<T, bool>
outside of the query:
Expression<Func<CastInfo, bool>> castPredicate = castCondition;
and then use
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));
or
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));
To let compiler infer the expression type, I would create a custom extension method like this:
using System;
using System.Linq.Expressions;
namespace LinqKit
{
public static class Extensions
{
public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr;
}
}
and then simply use
var castPredicate = castCondition.ToExpression();
It still has to be done outside of the query, i.e. the following does not work:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));