[Программирование, .NET, C#] Cоздание переиспользуемых Linq фильтров (построителей предикатов для Where), которые можно применять для разных типов
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Способ создания переиспользуемых Linq фильтров (построителей предикатов для условия Where), которые можно применять для разных типов объектов. Поля объектов для фильтрации указываются с помощью MemberExpression.Способ подходит для Entity Framework, включая Async операции.Основная идея. Что такое переиспользуемый фильтр?Например есть приказы:
class Order {
public DateTime Start { get; set; }
public DateTime? End { get; set; }
}
Пусть нужно найти все приказы которые будут действовать в ближайшие 7 дней.С помощью переиспользуемого построителя фильтра (если бы он был реализован) найти приказы можно так:
var ordersFiltred = orders
.WhereOverlap(
// с помощью MemberExpressions
// указываем по каким полям производить поиск
fromField: oo => oo.Start,
toField: oo => oo.End,
// указываем период поиска
from: DateTime.Now,
to: DateTime.Now.AddDays(7))
.ToList();
Этот же WhereOverlap можно переиспользовать и применить к другому типу. Например, для поиска командировок:
class Trip {
public DateTime? From { get; set; }
public DateTime? To { get; set; }
}
var tripsFiltred = trips
.WhereOverlap(
// с помощью MemberExpressions
// указываем по каким полям производить поиск
fromField: oo => oo.From,
toField: oo => oo.To,
from: DateTime.Now,
to: DateTime.Now.AddDays(7))
.ToList();
Приказы и командировки - это разные типы объектов, у них нет общего интерфейса, поля для поиска называются по-разному. И все таки для обоих типов (и приказов и командировок) применяется один переиспользуемый фильтр WhereOverlap.Ниже описано как можно делать такие переиспользуемые построители предикатов.Как сделать переиспользуемый фильтрВыше было описано применение WhereOverlap, было бы логично показать его реализацию. Но для того, чтобы сделать WhereOverlap, нужна реализация операторов “И”, “ИЛИ”. Поэтому начнем с более простого примера.Пусть есть выплаты и премии:
class Payout {
public decimal Total { get; set; }
public bool UnderControl { get; set; }
}
class Premium {
public decimal Sum { get; set; }
public bool RequiresConfirmation { get; set; }
}
Сделаем переиспользуемый фильтр для поиска платежей больше определенной суммы:
class UnderControlPayFilter {
readonly decimal Limit;
public UnderControlPayFilter(decimal limit) {
Limit = limit;
}
public Expression<Func<TEnt, bool>> Create<TEnt>(
Expression<Func<TEnt, decimal>> sumField) {
// GreaterOrEqual - нужно реализовать
// GreaterOrEqual - это extension, который принимает
// - указание на поле (Expression sumField)
// - и значение с которым нужно сравнивать (Limit)
return sumField.GreaterOrEqual(Limit);
}
}
Пример использования UnderControlPayFilter фильтра:
// фильтр поиска платежей требующих дополнительного контроля
//
// конкретный предел (здесь 1000) можно вынести в настройки,
// а UnderControlPayFilter зарегистрировать в IoC-контейнере.
// Тогда можно централизовано (через найстройки приложения)
// управлять максимальным пределом
var underControlPayFilter = new UnderControlPayFilter(1000);
//
// Применение переиспользуемого фильтра для выплат
var payoutPredicate =
underControlPayFilter.Create<Payout>(pp => pp.Total);
// здесь, для упрощения, payouts - это массив,
// в реальном приложении это может быть Entity Framework DbSet
var payouts = new[] {
new Payout{ Total = 100 },
new Payout{ Total = 50, UnderControl = true },
new Payout{ Total = 25.5m },
new Payout{ Total = 1050.67m }
}
.AsQueryable()
.Where(payoutPredicate)
.ToList();
//
// Применение переиспользуемого фильтра для премий
var premiumPredicate =
underControlPayFilter.Create<Premium>(pp => pp.Sum);
// здесь, для упрощения, premiums - это массив,
// в реальном приложении это может быть Entity Framework DbSet
var premiums = new[] {
new Premium{ Sum = 2000 },
new Premium{ Sum = 50.08m },
new Premium{ Sum = 25.5m, RequiresConfirmation = true },
new Premium{ Sum = 1070.07m }
}
.AsQueryable()
.Where(premiumPredicate)
.ToList();
Все готово, осталось только реализовать GreaterOrEqual extension:
public static class MemberExpressionExtensions {
public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>(
this Expression<Func<TEnt, TProp>> field, TProp val)
=> Expression.Lambda<Func<TEnt, bool>>(
Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))),
field.Parameters);
}
По аналогии можно реализовать extension-ы LessOrEqual, Equal, HasNoVal и другие.Более сложные переиспользуемые фильтры с операторами “И” и “ИЛИ”Пускай в выборку должны попадать не только платежи больше определенного предела, но и те, которые специально отмечены, как требующие дополнительного контроля.Дополним UnderControlPayFilter:
class UnderControlPayFilter {
readonly decimal Limit;
public UnderControlPayFilter(decimal limit) {
Limit = limit;
}
public Expression<Func<TEnt, bool>> Create<TEnt>(
Expression<Func<TEnt, decimal>> sumField,
Expression<Func<TEnt, bool>> controlMarkField) {
// PredicateBuilder нужно реализовать (см. ниже)
return PredicateBuilder.Or(
sumField.GreaterOrEqual(Limit),
controlMarkField.Equal(true));
}
}
Пример использования:
// для выплат
var payoutPredicate =
underControlPayFilter.Create<Payout>(
sumField: pp => pp.Total,
controlMarkField: pp => pp.UnderControl);
// для премий
var premiumPredicate =
underControlPayFilter.Create<Premium>(
sumField: pp => pp.Sum,
controlMarkField: pp => pp.RequiresConfirmation);
PredicateBuilder это “A universal PredicateBuilder” сделанный Pete Montgomery.ЗаключениеЧтобы делать свои переиспользуемые фильтры, нужен только PredicateBuilder и MemberExpressionExtensions. Просто скопируйте их в свой проект. Переиспользуемые фильтры можно оформить как extension (как WhereOverlap), как статический хелпер или класс (как UnderControlPayFilter).Я сделал парочку переиспользуемых фильтров - GitHub, NuGet (включает PredicateBuilder и MemberExpressionExtensions).
===========
Источник:
habr.com
===========
Похожие новости:
- [IT-инфраструктура, Service Desk, Управление проектами, Карьера в IT-индустрии] Как мы развернули круглосуточную техническую поддержку с нуля всего за 1 год
- [Информационная безопасность, .NET, PowerShell, Visual Basic for Applications] Созданные с помощью библиотеки .NET документы Excel обходят проверки безопасности (перевод)
- [Программирование, Assembler] Считывание контроллера nes (dendy) на ассемблере
- [Программирование, Совершенный код, Управление проектами] Сбер запускает публичный code review
- [.NET, C#, Профессиональная литература] Книга «C# 8 и .NET Core. Разработка и оптимизация»
- [Python, Программирование] Как ускорить код на Python в тысячу раз (перевод)
- [Программирование, DevOps] 20 лучших практик по работе с Docker-файлами (перевод)
- [Программирование микроконтроллеров, Разработка под Arduino, 3D-принтеры, DIY или Сделай сам] MIDI браслет для управления синтезаторами (в основном — для органично звучащего вибрато)
- [PHP, Программирование] arm64 vs x86_64 для php (перевод)
- [Программирование, Java, Проектирование и рефакторинг] Паттерн проектирования Builder (Строитель) в Java (перевод)
Теги для поиска: #_programmirovanie (Программирование), #_.net, #_c#, #_entityframework, #_entity, #_entity_framework, #_expression, #_expression_trees, #_expression_tree, #_predicate, #_linq, #_.net, #_entity_framework_core, #_programmirovanie (
Программирование
), #_.net, #_c#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:28
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Способ создания переиспользуемых Linq фильтров (построителей предикатов для условия Where), которые можно применять для разных типов объектов. Поля объектов для фильтрации указываются с помощью MemberExpression.Способ подходит для Entity Framework, включая Async операции.Основная идея. Что такое переиспользуемый фильтр?Например есть приказы: class Order {
public DateTime Start { get; set; } public DateTime? End { get; set; } } var ordersFiltred = orders
.WhereOverlap( // с помощью MemberExpressions // указываем по каким полям производить поиск fromField: oo => oo.Start, toField: oo => oo.End, // указываем период поиска from: DateTime.Now, to: DateTime.Now.AddDays(7)) .ToList(); class Trip {
public DateTime? From { get; set; } public DateTime? To { get; set; } } var tripsFiltred = trips
.WhereOverlap( // с помощью MemberExpressions // указываем по каким полям производить поиск fromField: oo => oo.From, toField: oo => oo.To, from: DateTime.Now, to: DateTime.Now.AddDays(7)) .ToList(); class Payout {
public decimal Total { get; set; } public bool UnderControl { get; set; } } class Premium { public decimal Sum { get; set; } public bool RequiresConfirmation { get; set; } } class UnderControlPayFilter {
readonly decimal Limit; public UnderControlPayFilter(decimal limit) { Limit = limit; } public Expression<Func<TEnt, bool>> Create<TEnt>( Expression<Func<TEnt, decimal>> sumField) { // GreaterOrEqual - нужно реализовать // GreaterOrEqual - это extension, который принимает // - указание на поле (Expression sumField) // - и значение с которым нужно сравнивать (Limit) return sumField.GreaterOrEqual(Limit); } } // фильтр поиска платежей требующих дополнительного контроля
// // конкретный предел (здесь 1000) можно вынести в настройки, // а UnderControlPayFilter зарегистрировать в IoC-контейнере. // Тогда можно централизовано (через найстройки приложения) // управлять максимальным пределом var underControlPayFilter = new UnderControlPayFilter(1000); // // Применение переиспользуемого фильтра для выплат var payoutPredicate = underControlPayFilter.Create<Payout>(pp => pp.Total); // здесь, для упрощения, payouts - это массив, // в реальном приложении это может быть Entity Framework DbSet var payouts = new[] { new Payout{ Total = 100 }, new Payout{ Total = 50, UnderControl = true }, new Payout{ Total = 25.5m }, new Payout{ Total = 1050.67m } } .AsQueryable() .Where(payoutPredicate) .ToList(); // // Применение переиспользуемого фильтра для премий var premiumPredicate = underControlPayFilter.Create<Premium>(pp => pp.Sum); // здесь, для упрощения, premiums - это массив, // в реальном приложении это может быть Entity Framework DbSet var premiums = new[] { new Premium{ Sum = 2000 }, new Premium{ Sum = 50.08m }, new Premium{ Sum = 25.5m, RequiresConfirmation = true }, new Premium{ Sum = 1070.07m } } .AsQueryable() .Where(premiumPredicate) .ToList(); public static class MemberExpressionExtensions {
public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>( this Expression<Func<TEnt, TProp>> field, TProp val) => Expression.Lambda<Func<TEnt, bool>>( Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))), field.Parameters); } class UnderControlPayFilter {
readonly decimal Limit; public UnderControlPayFilter(decimal limit) { Limit = limit; } public Expression<Func<TEnt, bool>> Create<TEnt>( Expression<Func<TEnt, decimal>> sumField, Expression<Func<TEnt, bool>> controlMarkField) { // PredicateBuilder нужно реализовать (см. ниже) return PredicateBuilder.Or( sumField.GreaterOrEqual(Limit), controlMarkField.Equal(true)); } } // для выплат
var payoutPredicate = underControlPayFilter.Create<Payout>( sumField: pp => pp.Total, controlMarkField: pp => pp.UnderControl); // для премий var premiumPredicate = underControlPayFilter.Create<Premium>( sumField: pp => pp.Sum, controlMarkField: pp => pp.RequiresConfirmation); =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_.net, #_c# |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:28
Часовой пояс: UTC + 5