Устранение дублирования Where Expressions в приложении
public class Category : HasIdBase
{
public static readonly Expression> NiceRating = x => x.Rating > 50;
//...
}
var niceCategories = db.Query.Where(Category.NiceRating);
К сожалению, этот номер не пройдет, если вы хотите выбрать продукты из соответствующих категорий:
public class Product: HasIdBase
{
public virtual Category Category { get; set; }
//...
}
var niceProductsCompilationError = db.Query.Where(Category.NiceRating); // так нельзя!
К счастью, устранить данный недостаток довольно просто!
// Фактически мы реализуем композицию выражений,
// которая даст нам выражение, соответствующее композиции целевых функций
public static Expression> Compose(
this Expression> input
, Expression> inOutOut
, TIn inParam = default(TIn))
{
// это параметр x => blah-blah. Для LINQ нам нужен null
var param = Expression.Parameter(typeof(TIn), inParam);
// получаем объект, к которому применяется выражение
var invoke = Expression.Invoke(input, param);
// и выполняем "получи объект и примени к нему его выражение"
var res = Expression.Invoke(inOutOut, invoke);
// возвращаем лямбду нужного типа
return Expression.Lambda>(res, param);
}
// Добавляем "продвинутый" вариант Where
public static IQueryable Where(this IQueryable queryable
, Expression> prop
, Expression> where)
{
return queryable.Where(prop.Compose(where));
}
// Проверяем
[Fact]
public void AdvancedWhere_Works()
{
var product = new Product(new Category() {Rating = 700}, "Some Product", 100500);
var q = new[] {product}.AsQueryable();
var values = q.Where(x => x.Category, Category.NiceRating).ToArray();
Assert.Equal(700, values[0].Category.Rating);
}
Спасибо за внимание!
Комментарии (9)
23 октября 2016 в 21:43
0↑
↓
А могли бы вы привести примеры «бизнес-процессов», а то само решение понятно, а вот почему именно так — нет.23 октября 2016 в 21:50
0↑
↓
Синтетический примерvar activeAccount = db.Query
().Where(x => x.IsActive && x.IsNotDeleted && x.Balance > 0 && x.LastVisited > new DateTime(2015, 01, 01) && x.SuperPuper > 100500 && x.Whatever) Сначала мы считали, что активные аккаунты, это те, что IsActive, потом ввели soft-delte, потом стали учитывать баланс, потом дату последнего посещения и пошло-поехало. Если эти правила не группировать, а копипастить, то рано или поздно где-то забудем поменять. Значит условия нужно группировать.
Из реальных кейсов бизнес-процессов, однажды клиент попросил формировать URL для товаров, добавленных до определенной даты одним способом, а после — другим.
23 октября 2016 в 21:59
+1↑
↓
Ваш ситетический — почти один в один мой реальный :)23 октября 2016 в 22:10
0↑
↓
Да, он много у кого есть такой. Вы совсем не одиноки.
23 октября 2016 в 23:14
0↑
↓
Может проще использовать SQL-запрос?23 октября 2016 в 23:33
0↑
↓
Как это решит задачу реиспользуемости бизнес-правил фильтрации? Будете SQL-строки собирать?24 октября 2016 в 00:46
+1↑
↓
Вам никто не мешает написать Visitor, который сконвертирует это в запрос к любому хранилищу — у меня в LDAP запросы так генерируются. Правда ушли от дефолтных Expression, что бы ограничить возможные варианты запросов типа MethodCallExpression и тд.24 октября 2016 в 00:49
0↑
↓
А чем заменили Expression? Свой API?24 октября 2016 в 01:02 (комментарий был изменён)
0↑
↓
Угу. Простенько и обрезано под бизнес область. Соответственно свой аналог IQueryable + IQueryProvider с минимальным функционалом. Но на LDAP или SQL легко раскладывать. И можно свои подтипы для IQueryable рисовать и ограничивать набор выражений используемых в запросах и добавлять специфику.Ноги отсюда растут — SCIM parser Это была отправная точка ;)