Search code examples
linqentity-framework-coreef-core-3.1

How to write condition based on expression?


I am trying to write condition based on expression. I have two classes Test1 and Test2.

public class Test1<TEntity, TProperty>
{
    public IQueryable<TEntity> Test(
        IQueryable<TEntity> queryable,
        Expression<Func<TEntity, TProperty>> expression,
        TProperty value
    )
    {
        MemberExpression memberExpression = (MemberExpression)(expression.Body);
        var propName = memberExpression.Member.Name;

        // Cannot apply operator '>' to operands of type 'P' and 'P'
        queryable = queryable.Where(e => EF.Property<TProperty>(e, propName) > value));
        return queryable;
    }
}

public class Test2<TEntity, TProperty>
    where TProperty : IComparable
{
    public IQueryable<TEntity> Test(
        IQueryable<TEntity> queryable,
        Expression<Func<TEntity, TProperty>> expression,
        TProperty value
    )
    {
        MemberExpression memberExpression = (MemberExpression)(expression.Body);
        var propName = memberExpression.Member.Name;

        // This one compiles
        queryable = queryable.Where(e => EF.Property<TProperty>(e, propName).CompareTo(value) > 0);
        return queryable;
    }
}

First one (Test1) tries to compare values with > but it does not compile. Second one (Test2) declares generic type as IComparable and uses .CompareTo() method inside where condition. This one compiles but on runtime it throws:

The LINQ expression 'DbSet<SortableEntity>
    .Where(s => s.IsDeleted == False)
    .Where(s => EF.Property<long>((object)s, "Id").CompareTo((object)__value_0) > 0)' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()

Is it possible to somehow write custom condition based on expression? I want property/member expression to be passed by user and then decide what condition to apply to IQueryable.


Solution

  • Well, EF.Property is last thing that you have to use in such situation. You have property, even correct body - use this:

    public class Test1<TEntity, TProperty>
    {
        public IQueryable<TEntity> Test(
            IQueryable<TEntity> queryable,
            Expression<Func<TEntity, TProperty>> expression,
            TProperty value
        )
        {
            var predicateLambda = Expression.Lambda<Func<TEntity, bool>>(
                Expression.GreaterThan(expression.Body, Expression.Constant(value, typeof(TProperty))), 
                expression.Parameters[0]);
    
            queryable = queryable.Where(predicateLambda);
            return queryable;
        }
    }