Can this function be modified to handle multiple property comparisons?

74 Views Asked by At

For reference, here is the original question (Keep in mind that the Filter() function originates from this post): Dynamic Where for List<T>

Original post's function for clarity:

 public static List<T> Filter<T>
        (this List<T> source, string columnName, 
         string compValue)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
        Expression property = Expression.Property(parameter, columnName);
        Expression constant = Expression.Constant(compValue);
        Expression equality = Expression.Equal(property, constant);
        Expression<Func<T, bool>> predicate =
            Expression.Lambda<Func<T, bool>>(equality, parameter);

        Func<T, bool> compiled = predicate.Compile();
        return source.Where(compiled).ToList();
    }

Which allows you to do this:

var people = new[] {
            new { FirstName = "John", LastName = "Smith" },
            new { FirstName = "John", LastName = "Smith" },
            new { FirstName = "John", LastName = "Noakes" },
            new { FirstName = "Linda", LastName = "Smith" },
            new { FirstName = "Richard", LastName = "Smith" },
            new { FirstName = "Richard", LastName = "Littlejohn" },
        }.ToList();

var filtered = people.Filter("LastName", "Smith");

But what if you have multiple properties you want to match on? For example if I wanted to filter for all people with FirstName = John and LastName = Smith

Pseduocode just to give the idea, I'm sure there's a more elegant way to code this:

var filtered = people.Filter(new [] {Column="FirstName", Value = "John"}, {Column="LastName", Value="Smith"});

Also curious if this could be easily adapted to handle any type for the property (i.e. object instead of always string like the example). I haven't worked with these expression trees much, I appreciate any help you can provide.

1

There are 1 best solutions below

6
DavidG On BEST ANSWER

You can combine multiple expressions using the AndAlso extension found here. Then change your filter method to this:

public static List<T> Filter<T>(this List<T> source, params (string columnName, string compValue)[] filters)
{
    Expression<Func<T, bool>> exp = null;
    
    foreach (var filter in filters)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
        Expression property = Expression.Property(parameter, filter.columnName);
        Expression constant = Expression.Constant(filter.compValue);
        Expression equality = Expression.Equal(property, constant);
        Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(equality, parameter);
        
        exp = exp is null ? predicate : exp.AndAlso(predicate);
    }

    Func<T, bool> compiled = exp.Compile();
    return source.Where(compiled).ToList();
}

And call it like this:

var results = people.Filter(("FirstName", "John"), ("LastName", "Smith"));

This was very quickly written so you may prefer not to use a list of tuples.