У меня есть классный человек, у которого есть некоторые агрегированные значения, например если идентификатор четный или нечетный.

public class Person
{
    public int Id { get; set; }
    // ... some properties
    public static Expression<Func<Person, NumberType>> NumberType
    {
        get
        {
            return p => ((p.Id % 2 == 0)
                ? Shared.NumberType.Even
                : Shared.NumberType.Odd);
        }
    }
}

public enum NumberType
{
    Even = 0,
    Odd = 1
}

Теперь, если я попытаюсь получить своих людей с четными идентификаторами

// this is DbSet<Person>
var people = dbContext.People.AsQueryable();

// this alone works
people = people.Where(p => ((p.Id % 2 == 0) ? Shared.NumberType.Even : Shared.NumberType.Odd) == Shared.NumberType.Even);

// this should do exactly the same as above but results in the following error as soon as the queryable is evaluated
var body = Expression.MakeBinary(ExpressionType.Equal, Person.LocalNumberType.Body, Expression.Constant(Shared.NumberType.Even));
people =  people.Where(Expression.Lambda<Func<Person, bool>>(body, Expression.Parameter(typeof(Person), "p")));


// exception thrown here
var evenCount = people.Count();

введите описание изображения здесь При обработке запроса возникло необработанное исключение. InvalidOperationException: выражение LINQ 'DbSet () .Где (p => (int) p.Id% 2 == 0? Четный: Нечетный == 0) .Where (p => p.Id% 2 == 0? Even: Odd == Even) 'не может быть переведено. Либо перепишите запрос в форме, которая может быть переведена, либо явно переключитесь на оценку клиента, вставив вызов AsEnumerable, AsAsyncEnumerable, ToList или ToListAsync. См. https://go.microsoft.com/fwlink/?linkid=2101038. для дополнительной информации.

// FUNFACT, ordering by the Expression works
var orderByExp = (typeof(Person).GetProperty("NumberType")!.GetValue(null, null) as LambdaExpression);
people = people.Provider.CreateQuery<Person>(Expression.Call(
    typeof(Queryable),
    "orderBy",
    new Type[] {
        typeof(Person),
        orderByExp.ReturnType
    },
    people.Expression,
    Expression.Quote(orderByExp)));

1
Marius Steinbach 27 Ноя 2021 в 18:33

1 ответ

Лучший ответ

Выражения параметров внутри лямбда-выражений идентифицируются по экземпляру, а не по имени, как вы думаете. Так вот

var body = Expression.MakeBinary(ExpressionType.Equal, Person.LocalNumberType.Body, Expression.Constant(Shared.NumberType.Even));
people =  people.Where(Expression.Lambda<Func<Person, bool>>(body, Expression.Parameter(typeof(Person), "p")));

Тело привязано к параметру исходного выражения Person.NumberType, но затем вы используете его внутри нового лямбда-выражения с другим параметром (событие, хотя это тот же тип и имя).

Одно из возможных решений - повторно использовать исходный параметр, например

var source = Person.LocalNumberType;
var body = Expression.Equal(source.Body, Expression.Constant(Shared.NumberType.Even));
var predicate = Expression.Lambda<Func<Person, bool>>(body, source.Parameters);

people =  people.Where(predicate);
1
Ivan Stoev 27 Ноя 2021 в 18:57