Home > database >  The LINQ expression 'DbSet<Person>() .Where(p => p.Id % 2 == 0 ? Even : Odd == Even)&#
The LINQ expression 'DbSet<Person>() .Where(p => p.Id % 2 == 0 ? Even : Odd == Even)&#

Time:11-28

I have a class person, which has some aggregated values, e.g. if the Id is Even or Odd.

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
}

Now If I try to get my people with even ids

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

enter image description here An unhandled exception occurred while processing the request. InvalidOperationException: The LINQ expression 'DbSet() .Where(p => (int)p.Id % 2 == 0 ? Even : Odd == 0) .Where(p => p.Id % 2 == 0 ? Even : Odd == Even)' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

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

CodePudding user response:

Parameter expressions inside lambda expressions are identified by instance, not by name as you seem to think. So here

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

the body is bound to the source Person.NumberType expression parameter, but then you are using it inside new lambda expression with different parameter (event though it is the same type and name).

One possible solution is to reuse the original parameter, e.g.

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);
  • Related