Страницы

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

понедельник, 11 февраля 2019 г.

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

Имеется модель 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")


Ответ

И так, вам нужен способ комбинировать выражения. Проще всего это сделать через операцию бета-редукции (т.е. подстановки фактического параметра на место формального).
Для этого понадобится вот такой простейший визитор (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Ну а теперь через операцию BetaReduce уже можно выражать полезные комбинаторы для выражений. Например, комбинатор And:
public static Expression> 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

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

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