EF 错误:无法翻译 LINQ 表达式。与生成的表达式一起发生,而不是与代码一起发生

问题描述 投票:0回答:1

在 .NET 8 中工作,我在存储库类上创建一个方法,该方法采用名为 Criteria 的对象并根据该对象中的条件查询 EF。 Criteria 能够生成一个表达式,然后将其传递给查询的Where() LINQ 扩展方法。 问题是,对于特定的表达式树,如果我提供与代码相同的表达式,我会得到一个异常,不会发生

表达式为:

x => x.UserRoles.Any(y => y.RoleNo == 100101)
,其中 x 为 UserEntity 类型,y 为 UserRoleEntity 类型,100101 可以是任何整数角色 ID。

产生的异常是:“System.InvalidOperationException:'无法翻译 LINQ 表达式 'y'。以可翻译的形式重写查询,或者通过插入对 'AsEnumerable' 的调用显式切换到客户端计算、“AsAsyncEnumerable”、“ToList”或“ToListAsync”。”

我还发现这个异常不清楚,因为它抱怨“y”,这是嵌套 lambda 的参数。 我无法想象这部分表达有什么问题。

public async Task<List<User>> GetByCriteria(ICriteria<User> criteria)
{
    // Using an expression in code
    var test = _dbContext.Set<UserEntity>()
        .Where(x => x.UserRoles.Any(y => y.RoleNo == 100101)).AsNoTracking();

    List<UserEntity> codeEntities = await test.ToListAsync();

上面的代码有效。 在这里,我在Where()调用中使用编码表达式。

public async Task<List<User>> GetByCriteria(ICriteria<User> criteria)
{
    Expression<Func<UserEntity, bool>> expression = criteria.AsExpression<UserEntity>();

    // Using a generated expression
    var queryable = _dbContext.Set<UserEntity>()
        .Where(expression).AsNoTracking();

    List<UserEntity> exprEntities = await queryable.ToListAsync();

当调用 ToListAsync() 时,此代码会产生异常。 变量 expression 是由 Criteria 对象生成的表达式。 在调试视图中,我可以看到这个表达式与之前代码中的表达式相同。

我尝试运行编码表达式与生成的 Expression 对象,这表明我使用的 lambda 是正确的。

我还尝试过使用和不使用 AsNoTracking() 调用来运行。 这没有什么区别。

我在调试器中比较了两个示例中的 Queryable 对象,发现它们之间没有差异。

我尝试在调试器之外运行,但仍然遇到异常。

如果我将两组示例代码放在一起,以便它首先运行编码的 lambda,然后运行生成的表达式,则异常不会发生。 我猜测这是因为 EF 已缓存第一个(成功的)查询,并且不会尝试再次查询第二个查询。

更新: 我研究了 LinqKit 的 PredicateBuilder,作为一种可能的方法来使我的代码更简单并使错误更容易找到。 不幸的是,它无法达到我所希望的效果。 它简化了顶级表达式(一系列逻辑 AND)的构建,但对于构建实际的条件表达式没有任何作用,这是这里的困难部分。 我需要能够获取动态属性名称、值和运算符并将它们组合在一起,因此看起来表达式 API 仍然是唯一的方法。

我将继续分析我的代码,希望找到编码和生成的表达式之间的差异。

c# entity-framework lambda expression .net-8.0
1个回答
0
投票

我在原来的帖子中发现了导致异常的问题。 事实证明,Gert Arnold 评论时的想法是正确的:

一个常见错误是在一个 lambda 表达式中范围变量的参数实例不同。如果它们具有相同的名称是不够的,它们应该是相同的实例。

我为每个谓词生成单独表达式的代码有一个编程错误,多次没有引起我的注意。 请参阅下面的方法中标记为“这就是问题”的行 - 它应该放置在 foreach 循环之前,而不是在其中。 public Expression AsExpression(ParameterExpression lambdaParameterEx) { Expression? conditionalEx = default; // Generate an Expression for testing the logical condition represented by the Criterion. bool isLast = true; foreach (var propertyName in NameParts.Reverse()) { // This is the thing we are comparing to. ParameterExpression parameterEx = Expression.Parameter(_entityType, "y"); /*** THIS IS THE PROBLEM ***/ if (isLast) { conditionalEx = GetComparisonExpression(parameterEx, propertyName); isLast = false; } else { // Create a lambda Expression from the conditional so that it can be embedded within the outer lambda Expression. // We have to call Expression.Lambda dynamically since it's a generic method and we don't know the type at compile time. Type lambdaGenericType = typeof(Func<,>).MakeGenericType(new Type[] { _entityType, typeof(bool) }); MethodInfo? lambdaMethodInfo = typeof(Expression) .GetMethods() .Where(m => m.Name == "Lambda" && m.IsGenericMethodDefinition && m.GetParameters() .Where(p => (p.Position == 0 && p.ParameterType == typeof(Expression)) || (p.Position == 1 && p.ParameterType == typeof(ParameterExpression[]))) .Count() == 2) .Single() .MakeGenericMethod(lambdaGenericType); if (lambdaMethodInfo != null) { Expression? lambdaEx = lambdaMethodInfo.Invoke( null, new object?[] { conditionalEx, new ParameterExpression[] { parameterEx } } ) as Expression; if (lambdaEx != null) { MemberExpression propertyEx = GetPropertyExpression(lambdaParameterEx, propertyName); // Wrap the lambda inside a call to Where(). Where() is called against the collection property. conditionalEx = GetWhereExpression(lambdaParameterEx, propertyEx, lambdaEx, lambdaGenericType); } } } } return conditionalEx!; }

这导致“y”的 ParameterExpression 创建两次,一次作为比较中的属性 [y.RoleNo == 100101],一次作为内部 lambda 的参数 [(y => ...)]。  由于这些 ParameterExpressions 未通过 .Equals() 测试,因此 EF 认为它们不同。

感谢所有为我提供想法的人。 我还必须感谢这篇文章

here

。 OP 的更新帖子包含一个很好的类,可以比较几乎任何两个表达式树。 通过在调试器中单步调试,我发现了比较失败的地方,这使我能够缩小问题根源的范围。

© www.soinside.com 2019 - 2024. All rights reserved.