使用导致异常的表达式过滤 EfCore DbSet
Filtering EfCore DbSet with expression causing exception
尝试使用生成的表达式过滤动态数据库集时
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;
生成的表达式
expression.ToString()
"c => c.Sources.Any(s => (s.Name == \"Intelligent Cotton Mouse\"))"
尝试执行 BaseQuery 时出现以下异常
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.
如果我尝试手动将生成的表达式插入到 .where()
(BaseQuery as IQueryable<Campaign>).Where(c =>
c.Sources.Any(s => s.Name == "Intelligent Cotton Mouse"));
有效,谓词成功转换为 sql。可能是什么问题?
问题显然出在动态构建的表达式中,根据我的经验,我可以打赌它出在某些 ParameterExpression
实例 中,例如 c
用于c =>
和c.Sources
,或s
用于s =>
和s.Name
。
请注意 ToString()
是在骗你,因为尽管它们在视觉上看起来是一样的,但实际上它们并非如此 - lambda 表达式参数是按实例而不是名称绑定的,lambda 表达式也是如此允许在构造过程中使用未绑定的参数表达式,并且标准情况下仅在尝试编译它们时才会生成错误。
这是一个为示例中的内部 Any
调用构建无效 lambda 表达式的示例(最后应该是生成相关异常的那个,因为对外部执行相同操作会产生不同的异常留言):
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);
请注意 obj
和 param
如何具有相同的名称和类型,但实例不同。即使没有错误并且 expr.ToString()
给出
s => (s.Name == "Abc")
尝试在 LINQ 查询中使用此表达式将产生运行时异常。
话虽如此,解决方案是修复表达式生成器代码以确保它使用正确的参数表达式。
例如,当从现有的 lambda 表达式组合时,它应该使用 Expression.Invoke
或自定义 ExpressionVisitor
将正文中任何地方使用的原始参数替换为新参数。有很多例子可以做到这一点,例如 .
尝试使用生成的表达式过滤动态数据库集时
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;
生成的表达式
expression.ToString()
"c => c.Sources.Any(s => (s.Name == \"Intelligent Cotton Mouse\"))"
尝试执行 BaseQuery 时出现以下异常
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.
如果我尝试手动将生成的表达式插入到 .where()
(BaseQuery as IQueryable<Campaign>).Where(c =>
c.Sources.Any(s => s.Name == "Intelligent Cotton Mouse"));
有效,谓词成功转换为 sql。可能是什么问题?
问题显然出在动态构建的表达式中,根据我的经验,我可以打赌它出在某些 ParameterExpression
实例 中,例如 c
用于c =>
和c.Sources
,或s
用于s =>
和s.Name
。
请注意 ToString()
是在骗你,因为尽管它们在视觉上看起来是一样的,但实际上它们并非如此 - lambda 表达式参数是按实例而不是名称绑定的,lambda 表达式也是如此允许在构造过程中使用未绑定的参数表达式,并且标准情况下仅在尝试编译它们时才会生成错误。
这是一个为示例中的内部 Any
调用构建无效 lambda 表达式的示例(最后应该是生成相关异常的那个,因为对外部执行相同操作会产生不同的异常留言):
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);
请注意 obj
和 param
如何具有相同的名称和类型,但实例不同。即使没有错误并且 expr.ToString()
给出
s => (s.Name == "Abc")
尝试在 LINQ 查询中使用此表达式将产生运行时异常。
话虽如此,解决方案是修复表达式生成器代码以确保它使用正确的参数表达式。
例如,当从现有的 lambda 表达式组合时,它应该使用 Expression.Invoke
或自定义 ExpressionVisitor
将正文中任何地方使用的原始参数替换为新参数。有很多例子可以做到这一点,例如