Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
421 views
in Technique[技术] by (71.8m points)

c# - Add two expressions to create a predicate in Entity Framework Core 3 does not work

I am trying to build an "And" predicate method using C# with Entity Framework Core 3 in a .NET Core application.

The function adds two expressions to each other and passes it to an IQueryable code:

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);
}

The call for of the function

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();

My code works fine in version EF Core 2, but after I updated the version to version 3 it throws the following exception

The LINQ expression 'Where( source: DbSet, predicate: (s) => (t => t.Id == "id1") && Invoke(t => t.Id == "id2") )' 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().

I cannot translate the query to Enumerable due to memory issues. I understand the problem but I don't know if there is a way to avoid it.

If anyone has a tip for me, I would appreciate that. Thanks a lot!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You need to unwrap the bodies of your lambdas, rather than using Expression.Invoke. You'll also have to rewrite at least one of the lambdas, so they both use the same parameters.

We'll use an ExpressionVisitor to replace right's parameter with the corresponding parameter from left. Then we'll construct the AndExpression using left's body and the rewritten body from right, and finally create a new 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);
}

This results in a lambda with the following DebugView:

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

Whereas your code results in a lambda with the following DebugView:

.Lambda #Lambda1<System.Func`2[System.String,System.Boolean]>(System.String $t) {
    $t == "id1" && .Invoke (.Lambda #Lambda2<System.Func`2[System.String,System.Boolean]>)($t)
}

.Lambda #Lambda2<System.Func`2[System.String,System.Boolean]>(System.String $t) {
    $t == "id2"
}

See how yours is calling a lambda from within a lambda (something that EF can't handle), whereas mine just has a single lambda.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...