Home > Blockchain >  Filtering EF query based on filter object automatically
Filtering EF query based on filter object automatically

Time:01-25

Let's say we have the following classes:

class A
{
    public int Id {get; set;}
    public string Name {get; set;}
} 

class FilterA
{
    public List<int> Ids {get; set;}
    public List<string> Names {get; set;}
}

I have an IQueryable which I need to filter based on values provided in FilterA in some generic way, because in real project I have to deal with there are hundreds of such objects each having tens of properties and I'm sick and tired of writing spaghetti code like this:

if(filter.Ids.Any())
{
    query = query.Where(q=>filter.Ids.Contains(q.Id));
}

if(filter.Names.Any())
{
    query = query.Where(q=>filter.Names.Contains(q.Name));
}  
...

I tried to implement this by myself couple times, but failed. The main problem from my perspective is that I can't specify the list of expressions returning different types, so ended up with some horrible, huge and unreadable reflection each time. So maybe I'm missing some obvious way of doing that or there are existing solutions for this already - anything will work unless it's not a mile of unreadable code, just don't want the cure to be worse than the disease.

Thanks in advance

CodePudding user response:

One thing you can do is to introduce extension method so checks can be turned into one liners/chained (actual method name is up to you):

public static class QueryableExts
{
    private static MethodInfo? _methodInfo = typeof(Enumerable)
        .GetMethod(nameof(Enumerable.Contains), 
            new []
            {
                typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
                Type.MakeGenericMethodParameter(0)
            });

    public static IQueryable<T> WhereInNotNullOrEmpty<T, TKey>(this IQueryable<T> q, Expression<Func<T, TKey>> selector, IEnumerable<TKey>? values)
    {
        if (values == null || !values.Any())
        {
            return q;
        }
        var collection = Expression.Constant(values);
        var contains = Expression.Call(_methodInfo.MakeGenericMethod(typeof(TKey)), collection, selector.Body);
        var expression = Expression.Lambda<Func<T, bool>>(contains, selector.Parameters);
        expression.Compile();
        return q.Where(expression);
    }
}

And usage:

query = query
    .WhereInNotNullOrEmpty(q => q.Id, filter.Ids)
    .WhereInNotNullOrEmpty(q => q.Name, filter.Names);

Other then that you are limited to using some code generation/reflection if you want to apply this filtering in more dynamic way. I would argue that using source generators can be more preferable approach then reflection for this in modern .NET.

  • Related