Skip to content

Library for filtering predicates dynamic generation

Notifications You must be signed in to change notification settings

Ask-DNA/DynamicFilters

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tutorial

Creation

To create a filter, you need to create a class that inherits from DynamicFilterBase, where T is the type of entity to be filtered:

class User
{
    public string Name { get; set; } = "";
    public int Age { get; set; } = 0;
}

class UserFilter : DynamicFilterBase<User>
{
    //...
}

Filter options declaration

A filter consists of options represented by its public fields and properties. To set an option, apply the FilterOptionAttribute to a class member:

class UserFilter : DynamicFilterBase<User>
{
    [FilterOption(Option = FilterOptionType.Inequality, TargetName = nameof(User.Name))]
    public string ExcludeName { get; set; } = "John Doe";

    [FilterOption(Option = FilterOptionType.GreaterThanOrEqual, TargetName = nameof(User.Age))]
    public int MinAge { get; set; } = 18;
}

The above class will generate the following expression: (User u) => u.Name != "John Doe" && u.Age >= 18

List of filter option types:

  • FilterOptionType.Equality
  • FilterOptionType.Inequality
  • FilterOptionType.GreaterThan
  • FilterOptionType.GreaterThanOrEqual
  • FilterOptionType.LessThan
  • FilterOptionType.LessThanOrEqual

If the filter option is FilterOptionType.Equality, then it can be omitted. It is also possible to omit the definition of the target member name if it is the same as the option name. Thus, the option declaration

[FilterOption(Option = FilterOptionType.Equality, TargetName = nameof(User.Age))]
public int Age { get; set; } = 18;

is similar to the following:

[FilterOption]
public int Age { get; set; } = 18;

Ignoring options

An option can refer to an ignore flag - a public property or field of boolean type:

class UserFilter : DynamicFilterBase<User>
{
    public bool IgnoreFlagForAgeFilterOption { get; set; } = false;

    [FilterOption(IgnoreFlagName = nameof(IgnoreFlagForAgeFilterOption))]
    public int Age { get; set; } = 18;
}

Ignore flag reference can be ommited if the flag name is "Ignore" + the option name:

class UserFilter : DynamicFilterBase<User>
{
    public bool IgnoreAge { get; set; } = false;

    [FilterOption]
    public int Age { get; set; } = 18;
}

When IgnoreAge is false, the above class will generate the following expression: (User u) => u.Age == 18

When IgnoreAge is true: (User u) => true

Usage

To generate a predicate, create an object and call the AsDelegate() or AsExpression() method:

UserFilter userFilter = new() { MinAge = 18 };
Func<User, bool> predicate = userFilter.AsDelegate();
Expression<Func<User, bool>> expression = userFilter.AsExpression();

The code below demonstrates filter usage example:

internal class Program
{
    class User
    {
        public string Name { get; set; } = "";
        public int Age { get; set; } = 0;
    }

    class UserFilter : DynamicFilterBase<User>
    {
        public bool IgnoreMinAge { get; set; } = false;

        [FilterOption(Option = FilterOptionType.GreaterThanOrEqual, TargetName = nameof(User.Age))]
        public int MinAge { get; set; } = 18;
    }

    static void Main(string[] args)
    {
        User[] users = [
            new User() { Name = "John", Age = 20 },
            new User() { Name = "Ann", Age = 18 },
            new User() { Name = "Robert", Age = 25 },
            new User() { Name = "Leonard", Age = 15 },
            new User() { Name = "Jane", Age = 14 },
            ];

        UserFilter userFilter = new() { MinAge = 18, IgnoreMinAge = false };
        PrintUserNames(users.Where(userFilter.AsDelegate()));
        // John Ann Robert

        userFilter.MinAge = 20;
        PrintUserNames(users.Where(userFilter.AsDelegate()));
        // John Robert

        userFilter.IgnoreMinAge = true;
        PrintUserNames(users.Where(userFilter.AsDelegate()));
        // John Ann Robert Leonard Jane
    }

    static void PrintUserNames(IEnumerable<User> users)
    {
        foreach (var user in users)
            Console.Write(user.Name + ' ');
        Console.WriteLine();
    }
}

Composite filters

Logic operations are defined for DynamicFilterBase class and IDynamicFilter interface, which result is a composite filter that implements the same interface.

Important

When using composite filters, keep in mind that they refer to the options of the original filters. Changing the values ​​of these options affects the behavior of the composite filter.

The following example demonstrates the process of creating and using a composite filter:

internal class Program
{
    class User
    {
        public string Name { get; set; } = "";

        public int Age { get; set; }
    }

    class MinAgeFilter : DynamicFilterBase<User>
    {
        [FilterOption(Option = FilterOptionType.GreaterThanOrEqual, TargetName = nameof(User.Age))]
        public int MinAge { get; set; }
    }

    class NameFilter : DynamicFilterBase<User>
    {
        [FilterOption(Option = FilterOptionType.Equality, TargetName = nameof(User.Name))]
        public string Name { get; set; } = "";
    }

    static void Main(string[] args)
    {
        User[] users = [
            new User() { Name = "John", Age = 20 },
            new User() { Name = "Ann", Age = 18 },
            new User() { Name = "Robert", Age = 25 },
            new User() { Name = "Leonard", Age = 15 },
            new User() { Name = "Jane", Age = 14 },
            ];

        IDynamicFilter<User> minAgeFilter = new MinAgeFilter() { MinAge = 18 };
        IDynamicFilter<User> nameFilter = new NameFilter() { Name = "John" };

        IDynamicFilter<User> compositeFilter = minAgeFilter & nameFilter;
        PrintUserNames(users.Where(compositeFilter.AsDelegate()));
        // John

        compositeFilter = minAgeFilter & !nameFilter;
        PrintUserNames(users.Where(compositeFilter.AsDelegate()));
        // Ann Robert

        compositeFilter = !minAgeFilter | nameFilter;
        PrintUserNames(users.Where(compositeFilter.AsDelegate()));
        // John Leonard Jane
    }

    static void PrintUserNames(IEnumerable<User> users)
    {
        foreach (var user in users)
            Console.Write(user.Name + ' ');
        Console.WriteLine();
    }
}

Exceptions

Predicate generation with AsDelegate() and AsExpression() methods can throw InvalidFilterConfigurationException or InvalidInnerFilterConfigurationException in case of invalid filter configuration.

Tip

To prevent throwing, check the Valid property.

Warning

Avoid throwing exceptions in ignore flag and filter option getters. When an exception occurs, it will be wrapped in a TargetInvocationException, which cannot be caught with the default IDE settings.

  • InvalidFilterConfigurationException : AggregateException

    Aggregates exceptions of type InvalidFilterOptionConfigurationException that occurred during filter configuration processing.

  • InvalidFilterOptionConfigurationException : InvalidOperationException

    Basic exception type to describe errors that occurs at the concrete filter option level.

  • TargetNotFoundException : InvalidFilterOptionConfigurationException

    Occurs when the specified target property or field is not found among public class members.

  • TypeMismatchException : InvalidFilterOptionConfigurationException

    Occurs when the types of the target member and the filter option do not match.

  • IgnoreFlagNotFoundException : InvalidFilterOptionConfigurationException

    Occurs when the specified ignore flag property or field is not found among public members of the filter.

  • OperatorIsRequiredException : InvalidFilterOptionConfigurationException

    Occurs when using types that do not support the operators required by the selected filter option type. For example, for FilterOptionType.GreaterThanOrEqual, the exception will be thrown when using a type that does not support the >= operator.

  • InvalidInnerFilterConfigurationException : InvalidOperationException

    Occurs when using composite filters, which inner filters are invalid.

About

Library for filtering predicates dynamic generation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages