Страницы

Поиск по вопросам

понедельник, 6 января 2020 г.

Как динамически сформировать выражение типа Expression<T, bool> для сервиса?

#c_sharp #linq


Имеется модель EF:

public class OrderDbModel
{
    public bool IsPayed { get; set; }

    public string Name { get; set; }

    public DateTime Created { get; set; }

    public bool IsDeleted { get; set; }
}


Ей соответствует доменная модель:

public class Order
{
    public bool IsPayed { get; set; }

    public string Name { get; set; }
}


Чтобы получить список доменных моделей заказов из сервиса в слое бизнес-логики приложения,
предполагается использование следующего класса-фильтра:

public class OrderFilter
{
    public bool GetUnpayed { get; set; }

    public IEnumerable Names { get; set; }
}


Для получения списка доменных моделей по фильтру создан сервис, который использует
репозиторий:

public class OrderService
{
    private readonly IOrderRepository _repository;

    public OrderService(IOrderRepository repository)
    {
        _repository = repository;
    }

    public IEnumerable GetFilteredOrders(OrderFilter filter)
    {
        // как динамически сформировать выражение???
        Expression> filterExpression = null; 

        var orders = _repository.Get(filterExpression);

        return Mapper.Map, IEnumerable>(orders);
    }
}


Собственно, сам вопрос - в комментариях в коде сервиса. Не получается динамически
сформировать filterExpression. Крайне интересен пример кода для данного конкретного
случая с подробным описанием. Имеются ли какие-то библиотеки, которые позволяют это
сделать с наименьшими трудозатратами?

PS: .NET Framework 4.6, C# 6.

Пример значений:
для OrderFilter со значениями { GetUnpayed = false, Names = new List { "Foo",
"Bar" } } должно плучиться выражение o => o.IsPayed && (o.Name == "Foo") || o.Name
== "Bar")
    


Ответы

Ответ 1



И так, вам нужен способ комбинировать выражения. Проще всего это сделать через операцию бета-редукции (т.е. подстановки фактического параметра на место формального). Для этого понадобится вот такой простейший визитор (visitor, "посетитель"): class Reducer : ExpressionVisitor { private readonly IDictionary arguments; public Reducer(IDictionary arguments) { this.arguments = arguments; } protected override Expression VisitParameter(ParameterExpression node) { Expression result; if (arguments.TryGetValue(node, out result)) return result; return base.VisitParameter(node); } } Даже если вы не сталкивались с классом ExpressionVisitor ранее - тут все просто. Класс Reducer заменяет в выражении параметры (объекты класса ParameterExpression ) на соответствующие им во входном словаре, если находит их там. Остальные узлы выражения он не трогает. Теперь можно определить операцию подстановки. Тут все просто: формируем словарь параметров и передаем его классу Reducer после чего прогоняем через Reducer тело лямбда-функции: public static Expression BetaReduce(this LambdaExpression expr, params Expression[] args) { Debug.Assert(expr.Parameters.Count == args.Length); var mapping = new Dictionary(); for (int i=0; i> And(params Expression>[] items) => And(items.AsEnumerable()); public static Expression> And(IEnumerable>> items) { var p = Expression.Parameter(typeof(T)); var body = items.Select(item => item.BetaReduce(p)).Aggregate(Expression.AndAlso); return Expression.Lambda>(body, p); } Или комбинатор Or: public static Expression> Or(params Expression>[] items) => Or(items.AsEnumerable()); public static Expression> Or(IEnumerable>> items) { var p = Expression.Parameter(typeof(T)); var body = items.Select(item => item.BetaReduce(p)).Aggregate(Expression.OrElse); return Expression.Lambda>(body, p); } Теперь вам ничего не помешает сложить все ваши условия в список, после чего объединить их все через And: var conditions = new List>>(); if (filter.GetUnpayed) conditions.Add(x => !x.IsPayed); ... var filterExpression = ExpressionUtils.And(conditions); Если же вам не нравится работать с выражениями в "сыром" виде, можете попробовать библиотеку LinqKit.

Комментариев нет:

Отправить комментарий