DEV Community

Anton Ilin
Anton Ilin

Posted on

Building Linq predicates in code

Just imagine you have a class and each field of it represents a filter, which you need to apply to database entities, using Entity Framework or any other ORM:

public class Animal
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Height { get; set; }
    public int Weight { get; set; }
}

public class AnimalFilter
{
    public string Name { get; set; }
    public int? Height { get; set; }
    public int? Weight { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

First option would be just check if filter field has a value and create predicate for each case:

if (!string.IsNullOrWhiteSpace(filter.Name) && !filter.Height.HasValue && !filter.Weight.HasValue)
{
    var predicate = a => a.Name == filter.Name;
}
...
Enter fullscreen mode Exit fullscreen mode

Disadvantage of this method is that you need to write custom predicates for all combinations of given parameters. For AnimalFilter it will be 8 different predicates. But what if filter has 10, 20 fields?

Another option is to use expressions. You can create expression based on a filter data and convert it to predicate:

var parameter = Expression.Parameter(typeof(Animal));
Expression expression = Expression.Constant(true);

if (!string.IsNullOrWhiteSpace(filter.Name))
{
    expression = Expression.AndAlso(
                    expression,
                    Expression.Equal(
                        Expression.Property(parameter, nameof(Animal.Name)),
                        Expression.Constant(filter.Name)
                    )
                )
}

if (filter.Height.HasValue))
{
    expression = Expression.AndAlso(
                    expression,
                    Expression.Equal(
                        Expression.Property(parameter, nameof(Animal.Height)),
                        Expression.Constant(filter.Height.Value)
                    )
                )
}

if (filter.Weight.HasValue))
{
    expression = Expression.AndAlso(
                    expression,
                    Expression.Equal(
                        Expression.Property(parameter, nameof(Animal.Weight)),
                        Expression.Constant(filter.Weight.Value)
                    )
                )
}

var predicate = Expression.Lambda<Func<Animal, bool>>(expression, parameter);
Enter fullscreen mode Exit fullscreen mode

Advantage of this method is that for every new property added to the filter you need to add only one condition with code and do not create all combinations with existing properties.

This code can be improved by moving expression building to the helper class:

public static class PredicateExtensions
    {
        public static Expression And<T>(this Expression expression, ParameterExpression parameter, string propertyName, T value)
        {
            return Expression.AndAlso(
                    expression,
                    Expression.Equal(
                        Expression.Property(parameter, propertyName),
                        Expression.Constant(value)
                    )
                );
        }
}
Enter fullscreen mode Exit fullscreen mode

Then this code is simplified to:

var parameter = Expression.Parameter(typeof(Animal));
Expression expression = Expression.Constant(true);

if (!string.IsNullOrWhiteSpace(filter.Name))
{
    expression = expression.And(parameter, nameof(Animal.Name), filter.Name);
}

if (filter.Height.HasValue))
{
    expression = expression.And(parameter, nameof(Animal.Height), filter.Height);
}

if (filter.Weight.HasValue))
{
    expression = expression.And(parameter, nameof(Animal.Weight), filter.Weight);
}

var predicate = Expression.Lambda<Func<Animal, bool>>(expression, parameter);
Enter fullscreen mode Exit fullscreen mode

One problem that could occur - if your filter property and entity property have different types (for example, nullable and not nullable). In this case you need to add generic T parameter to the constant expression:

public static class PredicateExtensions
    {
        public static Expression And<T>(this Expression expression, ParameterExpression parameter, string propertyName, T value)
        {
            return Expression.AndAlso(
                    expression,
                    Expression.Equal(
                        Expression.Property(parameter, propertyName),
                        Expression.Constant(value, typeof(T))
                    )
                );
        }
}
Enter fullscreen mode Exit fullscreen mode

GLHF

Top comments (0)