在 Entity Framework Core 3 中添加两个表达式来创建谓词不起作用

Sam*_*our 6 c# expression entity-framework-core

我正在尝试在 .NET Core 应用程序中使用 C# 和 Entity Framework Core 3 构建“And”谓词方法。

该函数将两个表达式相加,并将其传递给 IQueryable 代码:

public Expression<Func<T, bool>> AndExpression<T>
                    (Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
      var andExpression = Expression.AndAlso(
           left.Body, Expression.Invoke(right,
           left.Parameters.Single()));

      return Expression.Lambda<Func<T, bool>>(andExpression, left.Parameters);
}
Run Code Online (Sandbox Code Playgroud)

函数的调用

Expression<Func<Entity, bool>> left = t => t.Id == "id1";
Expression<Func<Entity, bool>> right = t => t.Id == "id2";
var exp = AndExpression(left, right);
this.dbContext.Set<Entity>().source.Where(exp).ToList();
Run Code Online (Sandbox Code Playgroud)

我的代码在 EF Core 2 版本中运行良好,但在我将版本更新到版本 3 后,它抛出以下异常

LINQ 表达式 'Where( source: DbSet, predicate: (s) => (t => t.Id == "id1") && Invoke(t => t.Id == "id2") )' 不能是翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。

由于内存问题,我无法将查询转换为 Enumerable。我明白这个问题,但我不知道是否有办法避免它。

如果有人给我提示,我将不胜感激。非常感谢!

can*_*on7 8

您需要解开 lambdas 的主体,而不是使用Expression.Invoke. 您还必须至少重写一个 lambda 表达式,以便它们都使用相同的参数。

我们将使用 anExpressionVisitor用左侧的相应参数替换右侧的参数。然后我们将构造AndExpressionusing left 的 body 和从 right 重写的 body,最后创建一个新的 lambda。

public class ParameterReplaceVisitor : ExpressionVisitor
{
    public ParameterExpression Target { get; set; }
    public ParameterExpression Replacement { get; set; }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == Target ? Replacement : base.VisitParameter(node);
    }
}

public static Expression<Func<T, bool>> AndExpression<T>(
    Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
    var visitor = new ParameterReplaceVisitor()
    {
        Target = right.Parameters[0],
        Replacement = left.Parameters[0],
    };

    var rewrittenRight = visitor.Visit(right.Body);
    var andExpression = Expression.AndAlso(left.Body, rewrittenRight);
    return Expression.Lambda<Func<T, bool>>(andExpression, left.Parameters);
}
Run Code Online (Sandbox Code Playgroud)

这会产生一个带有以下 DebugView 的 lambda:

public class ParameterReplaceVisitor : ExpressionVisitor
{
    public ParameterExpression Target { get; set; }
    public ParameterExpression Replacement { get; set; }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == Target ? Replacement : base.VisitParameter(node);
    }
}

public static Expression<Func<T, bool>> AndExpression<T>(
    Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
    var visitor = new ParameterReplaceVisitor()
    {
        Target = right.Parameters[0],
        Replacement = left.Parameters[0],
    };

    var rewrittenRight = visitor.Visit(right.Body);
    var andExpression = Expression.AndAlso(left.Body, rewrittenRight);
    return Expression.Lambda<Func<T, bool>>(andExpression, left.Parameters);
}
Run Code Online (Sandbox Code Playgroud)

而您的代码会生成带有以下 DebugView 的 lambda:

.Lambda #Lambda1<System.Func`2[System.String,System.Boolean]>(Entity $t) {
    $t.Id == "id1" && $t.Id == "id2"
}
Run Code Online (Sandbox Code Playgroud)

看看你的如何从 lambda 中调用 lambda(EF 无法处理的东西),而我的只有一个 lambda。