코드에서 Linq 술어 빌드

클래스가 있고 각 필드가 Entity Framework 또는 다른 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; }
}


첫 번째 옵션은 필터 필드에 값이 있는지 확인하고 각 경우에 대한 술어를 만드는 것입니다.

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


이 방법의 단점은 주어진 매개변수의 모든 조합에 대해 사용자 정의 술어를 작성해야 한다는 것입니다. AnimalFilter의 경우 8개의 다른 술어가 됩니다. 그러나 필터에 10, 20개의 필드가 있으면 어떻게 될까요?

또 다른 옵션은 표현식을 사용하는 것입니다. 필터 데이터를 기반으로 식을 만들고 조건자로 변환할 수 있습니다.

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);


이 방법의 장점은 필터에 추가되는 모든 새 속성에 대해 코드와 함께 하나의 조건만 추가하고 기존 속성과의 모든 조합을 만들 필요가 없다는 것입니다.

이 코드는 표현식 작성을 도우미 클래스로 이동하여 개선할 수 있습니다.

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)
                    )
                );
        }
}


그런 다음 이 코드는 다음과 같이 단순화됩니다.

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);


발생할 수 있는 한 가지 문제 - 필터 속성과 엔터티 속성의 유형이 서로 다른 경우(예: nullable 및 not nullable). 이 경우 상수 표현식에 일반 T 매개변수를 추가해야 합니다.

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

좋은 웹페이지 즐겨찾기