Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Turn property access Expression into a predicate Expression

I'm trying to make a generic filtering class, that will receive a property accessor and check if it's within allowed values. So the signature of the filter would be:

class PropertyFilter<TItem, TProperty>
{
    PropertyFilter(Expression<Func<TItem, TProperty>> accessor, IEnumerable<TProperty> allowedValues);

    IQueryable<TItem> Filter(IQueryable<TItem> items);
}

and usage

var filter = new PropertyFilter<Entity, string>(e => e.Name, new [] { "NameA", "NameB" });

await filter.Filter(dbContext.Entities).ToListAsync();

The thing has to be IQueryable compatible, so I need to compose an expression. From an expression of form x => x.Property I need to create an expression Expression<Func<TItem, bool>> equivalent to x => allowedValues.Contains(x.Property). From what I've seen I am able to do basicaly anything with the expression using Visitors and whatnot, but for one I don't know what are the rules of translating expression to SQL and what I cannot do or I break it, and also the use-case seems too simple to warrant that much code as would go into implementing my own visitors and testing all of that. Is there a short way to do this or maybe a reliable library that has already figured it out and is compatible with .NET Core 3.0 and EF Core 3.0 previews?

like image 571
V0ldek Avatar asked Jan 21 '26 06:01

V0ldek


1 Answers

Untested, but this should work?

static Expression<Func<TItem, bool>> Contains<TItem, TProperty>(
    Expression<Func<TItem, TProperty>> accessor,
    IEnumerable<TProperty> allowedValues)
{
    var wrapped = new { allowedValues };
    var body = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains),
        new[] { typeof(TProperty) },
        Expression.PropertyOrField(Expression.Constant(wrapped), nameof(allowedValues)),
        accessor.Body);
    return Expression.Lambda<Func<TItem, bool>>(body, accessor.Parameters);
}

You should be able to pass the result of this to Queryable.Where.

Note that wrapped here is to add a layer of indirection that the receiving LINQ query parser might want.

like image 59
Marc Gravell Avatar answered Jan 22 '26 21:01

Marc Gravell