Building Linq predicates in code
Tags: dotnet, csharp, orm, expressions
Published: 2021-04-28 10:35:38

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:

```csharp
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; }
}
```
First option would be just check if filter field has a value and create predicate for each case:
```csharp
if (!string.IsNullOrWhiteSpace(filter.Name) && !filter.Height.HasValue && !filter.Weight.HasValue)
{
    var predicate = a => a.Name == filter.Name;
}
...
```
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:
```csharp
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);
```

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:
```csharp
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)
                    )
                );
        }
}
```
Then this code is simplified to:
```csharp
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);
```
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:
```csharp
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))
                    )
                );
        }
}
```
GLHF