Ошибка при попытке использовать дерево выражений в предложении Where () при использовании EFCore - PullRequest
0 голосов
/ 04 октября 2018

Я получаю сообщение об ошибке при попытке составить Expression<Func<ApplicationUser, bool>> путем объединения других выражений.Намерение состоит в том, чтобы использовать его в предложении where () запроса EFCore.Пример:

// Only get users that have been forwarded the request...
var request = GetSomeRequestToFilterOn();
var filter = HasRequestBeenForwaredToUserExpression().ReplaceParameter(request);
var results = _context.ApplicationUsers.Where(filter).ToList();

В приведенном выше примере я конвертирую Expression<Func<ApplicationUser, TransferRequest, bool>> в * и 1006 *, заменяя параметр TransferRequest в выражении на предоставленный.Это отлично работает в моих модульных тестах, но EFCore, похоже, не нравится, и я получаю эту ошибку при работе с реальной базой данных:

System.InvalidOperationException: переписывает дочернее выражение из типа System.Nullable<System.DateTime> печатать System.Collections.Generic.IEnumerable<System.Nullable<System.DateTime>> не разрешается, поскольку это изменит смысл операции.Если это сделано намеренно, переопределите «VisitUnary» и измените его, чтобы разрешить перезапись.в System.Linq.Expressions.ExpressionVisitor.ValidateChildType (Тип до, Тип после, String methodName) в System.Linq.Expressions.ExpressionVisitor.ValidateUnary (UnaryExpression до, UnaryExpression после) в System.Linq.Expressions.ExpressionVary_VisitorVary_Vision) в System.Linq.Expressions.UnaryExpression.Accept (посетитель ExpressionVisitor) в System.Linq.Expressions.ExpressionVisitor.Visit (узел Expression) в ...

По сути, считается, что человек имеетбыл «переадресован» запрос на передачу, если после последнего события «в ожидании утверждения» есть какие-либо «переадресованные» события, т. е. когда происходит новое событие «в ожидании утверждения», предыдущие «пересланные» события больше не учитываются (но все еще существуют).

Вот мой фильтр.

internal static Expression<Func<ApplicationUser, TransferRequest, bool>> HasRequestBeenForwaredToUserExpression()
{
    //only "Forwarded" events after the last "Awaiting Approval" event count.
    //so we need to get the last "Awaiting Approval" event from the request.
    var projection = LastAwaitingApprovalEvent();

    var userParam = Expression.Parameter(typeof(ApplicationUser));
    var requestParam = Expression.Parameter(typeof(TransferRequest));
    var requestToEvent = projection.Body.ReplaceParameter(projection.Parameters[0], requestParam);

    Expression<Func<ApplicationUser, TransferRequest, TransferRequestEvent, bool>> condition =
       (user, rqst, evt) => evt != null && rqst.TransferRequestEvents
            .Any(e2 =>
                Equals(e2.EventType, EventType.Forwarded) &&
                Equals(e2.User, user) &&
                e2.EventDateTime > evt.EventDateTime);

    var body = condition.Body
            .ReplaceParameter(condition.Parameters[0], userParam)
            .ReplaceParameter(condition.Parameters[1], requestParam)
            .ReplaceParameter(condition.Parameters[2], requestToEvent);
    return Expression.Lambda<Func<ApplicationUser, TransferRequest, bool>>(body, userParam, requestParam);
}

Этот метод получает последнее событие «Ожидание утверждения»

internal static Expression<Func<TransferRequest, TransferRequestEvent>> LastAwaitingApprovalEvent()
{
    return t => t.TransferRequestEvents
        .OrderBy(e => e.EventDateTime).ThenBy(e => e.Id)
        .LastOrDefault(e => Equals(e.EventType, EventType.AwaitingReview));
}

У меня есть класс ExpressionUtility, который выполняет работузаменяющих параметров:

public class ExpressionUtility
{
    public static Expression ReplaceParameter(this Expression expression,
        ParameterExpression toReplace,
        Expression newExpression)
    {
        return new ParameterReplaceVisitor(toReplace, newExpression)
            .Visit(expression);
    }

    public static Expression<Func<TArg2, TReturn>> ReplaceParameter<TArg1, TArg2, TReturn>(this Expression<Func<TArg1, TArg2, TReturn>> source, TArg1 arg1)
    {
        var t1Param = Expression.Constant(arg1);
        var t2Param = Expression.Parameter(typeof(TArg2));
        var body = source.Body
                    .ReplaceParameter(source.Parameters[0], t1Param)
                    .ReplaceParameter(source.Parameters[1], t2Param);
        return Expression.Lambda<Func<TArg2, TReturn>>(body, t2Param);
    }

    public static Expression<Func<TArg1, TReturn>> ReplaceParameter<TArg1, TArg2, TReturn>(this Expression<Func<TArg1, TArg2, TReturn>> source, TArg2 arg2)
    {
        var t1Param = Expression.Parameter(typeof(TArg1));
        var t2Param = Expression.Constant(arg2);
        var body = source.Body
                    .ReplaceParameter(source.Parameters[0], t1Param)
                    .ReplaceParameter(source.Parameters[1], t2Param);
        return Expression.Lambda<Func<TArg1, TReturn>>(body, t1Param);
    }
}

public class ParameterReplaceVisitor : ExpressionVisitor
{
    private ParameterExpression from;
    private Expression to;
    public ParameterReplaceVisitor(ParameterExpression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == from ? to : base.VisitParameter(node);
    }
}

edit

WhCommon crawl ru Я печатаю окончательную строку выражения в консоли.Я получаю

{Param_0 => ((значение (Debugging.Models.TransferRequest) .TransferRequestEvents.OrderBy (e => e.EventDateTime) .ThenBy (e => e.Id) .LastOrDefault (e => Equals (e.EventType, EventType.AwaitingReview))! = null) AndAlso value (Debugging.Models.TransferRequest) .TransferRequestEvents.Any (e2 => ((Equals (e2.EventType, EventType.Forwarded) AndAlsoe2.User, Param_0)) AndAlso (e2.EventDateTime> значение (Debugging.Models.TransferRequest) .TransferRequestEvents.OrderBy (e => e.EventDateTime) .ThenBy (e => e.Id) .LastOrDefals (e>(e.EventType, EventType.AwaitingReview)). EventDateTime))))}

Кроме того, следующий код работает просто отлично

Expression<Func<string, ApplicationUser, bool>> filter = (s, u) => u.FirstName.Contains(s);
var whereClause= filter.ReplaceParameter("mark");
_context.AppicationUsers.Where(whereClause).ToList();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...