When trying to filter dynamic dbset with generated expression
var expression = new ExpressionBuilder.ExpressionBuilder(BaseQuery.ElementType,TreeData.Filter).Build<T>();
Logger.LogDebug("Expression builder generated expression:\n{0}", expression.ToString());
BaseQuery = BaseQuery.Where(expression);
return this;
Generated expression
expression.ToString()
"c => c.Sources.Any(s => (s.Name == \"Intelligent Cotton Mouse\"))"
When trying to execute BaseQuery i get following exception
System.InvalidOperationException: The LINQ expression 'DbSet<Source>
.Where(s => EF.Property<Nullable<Guid>>((EntityShaperExpression:
EntityType: Campaign
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
), "Id") != null && EF.Property<Nullable<Guid>>((EntityShaperExpression:
EntityType: Campaign
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
), "Id") == EF.Property<Nullable<Guid>>(s, "CampaignId"))
.Any(s => s.Name == "Intelligent Cotton Mouse")' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
If i try to manually insert generated expression to .where()
(BaseQuery as IQueryable<Campaign>).Where(c =>
c.Sources.Any(s => s.Name == "Intelligent Cotton Mouse"));
it works, and predicate successfully translating to sql. What could be the problem?
The problem apparently is in the dynamically built expression, and based on my experience I can bet it is in some of the ParameterExpression
instances, like c
used in c =>
and c.Sources
, or s
used in s =>
and s.Name
.
Note that ToString()
is lying to you, because even though they look visually one and the same, in fact they aren't - lambda expression parameters are bound by instance, not name, and also lambda expressions allow having unbound parameter expressions during the construction, and standardly an error is generated only when trying to compile them.
Here is an example of building invalid lambda expression for the inner Any
call in your sample (which at the end should be the one generating the exception in question, since doing the same with outer gives different exception message):
var obj = Expression.Parameter(typeof(Source), "s");
var body = Expression.Equal(
Expression.Property(obj, "Name"),
Expression.Constant("Abc"));
var param = Expression.Parameter(typeof(Source), "s");
var expr = Expression.Lambda<Func<Source, bool>>(body, param);
Note how obj
and param
are with one and the same name and type, but different instances. And even though there is no error and expr.ToString()
gives
s => (s.Name == "Abc")
trying to use this expression in LINQ query will generate runtime exception.
With that being said, the solution is to fix the expression builder code to make sure it uses correct parameter expressions.
For instance, when composing from existing lambda expressions, it should use Expression.Invoke
or custom ExpressionVisitor
to replace the original parameter anywhere used in the body with a new parameter. There are many examples how you can do that, for instance Combine two lambda expressions with inner expression.