Добавление двух выражений для создания предиката в Entity Framework Core 3 не работает - PullRequest
2 голосов
/ 30 октября 2019

Я пытаюсь создать метод предиката «И» с использованием C # с Entity Framework Core 3 в приложении .NET Core.

Функция добавляет два выражения друг к другу и передает их в код 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);
}

Вызов функции

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

Мой код прекрасно работает в версии EF Core 2, но после обновления версии до версии 3 он выдает следующее исключение

Выражение LINQ 'Где (источник: DbSet, предикат: (s) => (t => t.Id == "id1") && Invoke (t => t.Id == "id2"))" не может быть переведено. Либо переписать запрос в форме, которую можно перевести, либо явно переключиться на оценку клиента, вставив вызов либо AsEnumerable (), AsAsyncEnumerable (), ToList (), либо ToListAsync ().

Я не могу перевести запрос в Enumerable из-за проблем с памятью. Я понимаю проблему, но не знаю, есть ли способ ее избежать.

Если у кого-нибудь есть совет для меня, я был бы признателен за это. Большое спасибо!

Ответы [ 2 ]

2 голосов
/ 30 октября 2019

Вам нужно развернуть тела своих лямбд, а не использовать Expression.Invoke. Вам также придется переписать хотя бы одну из лямбд, чтобы они оба использовали одинаковые параметры.

Мы будем использовать ExpressionVisitor, чтобы заменить параметр right соответствующим параметром left. Затем мы сконструируем AndExpression, используя тело слева и переписанное тело справа, и, наконец, создадим новую лямбду.

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

Это приведет к лямбде со следующим DebugView:

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

В то время как ваш код приводит к лямбде со следующим 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"
}

Посмотрите, как ваш вызывает лямбда изнутри лямбда (то, что EF не может обработать), тогда как мой просто имеетодна лямбда.

0 голосов
/ 30 октября 2019

Проблема здесь в том, что ваш запрос не работал или, по крайней мере, не так, как вы задумывали в EF Core 2 - в EF3 он вызовет исключение для таких вещей из-за изменения поведения, в EF Core 2 этомолча потянул бы в память и сделал бы там операцию, так что по сути ваш запрос всегда вытягивал данные в память и выполнял там операцию. Однако теперь он говорит вам, что он сделает это, и хочет, чтобы вы явно установили его или изменили выражение так, чтобы оно могло правильно переводиться в оператор SQL.

Это на самом деле довольно хороший примерпочему команда сделала это, по-видимому, вы не знали, что ранее она вытягивала все данные в память и выполняла там операции - теперь, когда вы знаете, что вы можете поработать над исправлением, чтобы оно выполняло операцию на сервере sql

Для получения дополнительной информации, не стесняйтесь читать Здесь

Модификация вашего кода, чтобы быть где-то вроде

public static Expression<Func<T, bool>> AndExpression
                    (this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
      var invoked = var invokedExpr = Expression.Invoke (right, left.Parameters.Cast<Expression> ());
      return Expression.Lambda<Func<T, bool>>
      (Expression.AndAlso (left.Body, invoked), left.Parameters);
}

, работая из памяти, такприведенный выше код не может быть идеальным - на работе, поэтому я не могу полностью проверить

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...