«Параметр не был связан в указанном выражении запроса LINQ to Entities.» Шаблон спецификации А - PullRequest
1 голос
/ 27 февраля 2020

Я следую описанной выше реализации шаблона спецификации здесь . У меня есть метод репозитория, который выглядит следующим образом:

public IEnumerable<MyDto> Find(Specification<Dto> specification)
{
    return myDbContext.MyDtos.Where(specification.ToExpression()).Take(20).ToList();
}

Если я использую обычную, не составную спецификацию, она работает просто отлично, но следующий сценарий завершается ошибкой с сообщением «Параметр« r »не был связан в указанное выражение запроса LINQ to Entities. ":

Specification<MyDto> spec = new Spec1(someCriterion)
    .And(new Spec2(someCriterion))
    .And(new Spec3(someCriterion))
    // etc...

var myDtos = repo.Find(spec);

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

Для справки, вот как класс AndSpecification<T> выглядит в моем коде:

public class AndSpecification<T> : Specification<T>
{
    private readonly Specification<T> _left;
    private readonly Specification<T> _right;

    public AndSpecification(Specification<T> left, Specification<T> right)
    {
        _left = left;
        _right = right;
    }

    public override Expression<Func<T, bool>> ToExpression()
    {
        Expression<Func<T, bool>> leftExpression = _left.ToExpression();
        Expression<Func<T, bool>> rightExpression = _right.ToExpression();

        BinaryExpression andExpression = Expression.AndAlso(
            leftExpression.Body, rightExpression.Body);

        return Expression.Lambda<Func<T, bool>>(
            andExpression, leftExpression.Parameters.Single());
    }
}

1 Ответ

3 голосов
/ 27 февраля 2020

Проблема в вашем методе ToExpression.

leftExpression и rightExpression - это каждый LambdaExpression, и у каждого из них есть свой отдельный T параметр.

Когда вы создаете LambdaExpression, который вы возвращаете из ToExpression, вы говорите, что для этого следует использовать параметр из leftExpression. Но как насчет параметра, который используется в rightExpression? rightExpression.Body содержит выражения, использующие rightExpression.Parameters[0], и они по-прежнему будут ссылаться на объект rightExpression.Parameters[0] даже после того, как вы возьмете rightExpression.Body и поместите его в другое выражение.

Вам нужно переписать rightExpression использовать тот же параметр, что и leftExpression. Самый простой способ сделать это - использовать ExpressionVisitor.

Сначала создайте ExpressionVisitor, который просто заменяет один параметр другим:

public class ParameterReplaceVisitor : ExpressionVisitor
{
    private readonly ParameterExpression target;
    private readonly ParameterExpression replacement;

    public ParameterReplaceVisitor(ParameterExpression target, ParameterExpression replacement) =>
        (this.target, this.replacement) = (target, replacement);

    protected override Expression VisitParameter(ParameterExpression node) =>
        node == target ? replacement : base.VisitParameter(node);
}

Затем используйте это для перезаписи вашего rightExpression.Body, поэтому он использует тот же объект параметров, что и leftExpression:

var visitor = new ParameterReplaceVisitor(rightExpression.Parameters[0], leftExpression.Parameters[0]);
var rewrittenRightBody = visitor.Visit(rightExpression.Body.Visit);
var andExpression = Expression.AndAlso(leftExpression.Body, rewrittenRightBody);

return Expression.Lambda<Func<T, bool>>(
    andExpression, leftExpression.Parameters[0]);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...