Dynamic Filtering of Collections using Expressions in C#

Filtering collections of data is a common task in software development. Often, you need to filter a collection of objects based on specific criteria or conditions. In this blog post, we’ll explore how to filter a collection of objects using dynamic filtering criteria with expressions in C#. We’ll build a flexible filtering mechanism that allows us to filter a list of Person objects based on different conditions such as “StartsWith” for names and numeric comparisons for ages.

Prerequisites

Before we dive into the code, make sure you have:

  1. A code editor such as Visual Studio or Visual Studio Code.
  2. Basic knowledge of C# and .NET.

The Scenario

Suppose we have a list of Person objects, and we want to filter this list based on various conditions. We don’t want to write separate filtering logic for each condition; instead, we want a flexible and reusable approach.

The Code

The Person Class

We start with a simple Person class that has Name and Age properties:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

The Filtering Criteria

We define a FilterCriteria class to hold our filtering conditions. Each condition consists of a PropertyName, an Operator, and a Value. For example, we may want to filter by Name starting with a certain character or Age greater than or equal to a specific value:

public class FilterCriteria<T>
{
    public List<FilterCondition<T>> Conditions { get; set; }
}

public class FilterCondition<T>
{
    public string PropertyName { get; set; }
    public string Operator { get; set; }
    public object Value { get; set; }
}

Dynamic Filtering with Expressions

Now, the heart of our filtering mechanism lies in the FilterCollection method. This method takes an IQueryable collection, a set of filtering criteria, and returns a filtered collection:

public static IQueryable<T> FilterCollection<T>(IQueryable<T> collection, FilterCriteria<T> criteria)
{
    var parameter = Expression.Parameter(typeof(T), "item");
    Expression expression = Expression.Constant(true);

    foreach (var condition in criteria.Conditions)
    {
        Expression binaryExpression;

        if (condition.Operator == "StartsWith")
        {
            // Create an expression for the StartsWith condition
            var property = Expression.Property(parameter, condition.PropertyName);
            var value = Expression.Constant(condition.Value);
            binaryExpression = Expression.Call(property, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), value);
        }
        else
        {
            // Handle other operators
            var property = Expression.Property(parameter, condition.PropertyName);
            var value = Expression.Constant(Convert.ChangeType(condition.Value, property.Type));
            binaryExpression = Expression.MakeBinary(GetExpressionType(condition.Operator), property, value);
        }

        expression = Expression.AndAlso(expression, binaryExpression);
    }

    var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter);
    return collection.Where(lambda);
}

In this method:

  • We create an Expression tree that represents the filtering logic.
  • We loop through each filtering condition and dynamically create the appropriate expression based on the condition’s operator.
  • For conditions with the “StartsWith” operator, we call the StartsWith method on the property.
  • For other operators (e.g., “Equal,” “GreaterThan”), we create binary expressions using the MakeBinary method.

Running the Filtering

Finally, in the Main method, we demonstrate how to use our dynamic filtering mechanism:

public static void Main()
{
    var people = new List<Person>
    {
        new Person { Name = "Alice", Age = 30 },
        new Person { Name = "Bob", Age = 25 },
        new Person { Name = "Charlie", Age = 40 },
        new Person { Name = "David", Age = 22 }
    };

    var filterCriteria = new FilterCriteria<Person>
    {
        Conditions = new List<FilterCondition<Person>>
        {
            new FilterCondition<Person> { PropertyName = "Name", Operator = "StartsWith", Value = "A" },
            new FilterCondition<Person> { PropertyName = "Age", Operator = "GreaterThanOrEqual", Value = 30 }
        }
    };

    var filteredPeople = FilterCollection(people.AsQueryable(), filterCriteria);

    foreach (var person in filteredPeople)
    {
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}

In this example, we filter the people collection based on two conditions: names starting with “A” and ages greater than or equal to 30.

Conclusion

Dynamic filtering of collections using expressions in C# provides a powerful and flexible way to filter data based on various conditions. With the FilterCollection method, you can easily filter collections of objects with different filtering criteria without writing separate filtering logic for each condition. This approach improves code reusability and maintainability, making your code more robust and efficient.

Feel free to extend this mechanism to handle additional operators or complex filtering scenarios based on your specific requirements. Happy coding!

Next Post Previous Post
No Comment
Add Comment
comment url