Допустим, у вас есть товары и категории. В какой-то момент клиент сообщает, что для категорий с рейтингом > 50 необходимо использовать другие бизнес-процессы. У вас достаточно опыта и вы понимаете, что где сегодня 50 завтра будет 127.37 и хотите избежать появления магических чисел в коде, поэтому делаете так:
public class Category : HasIdBase<int>
{
public static readonly Expression<Func<Category, bool>> NiceRating = x => x.Rating > 50;
//...
}
var niceCategories = db.Query<Category>.Where(Category.NiceRating);
К сожалению, этот номер не пройдет, если вы хотите выбрать продукты из соответствующих категорий:
public class Product: HasIdBase<int>
{
public virtual Category Category { get; set; }
//...
}
var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating); // так нельзя!
К счастью, устранить данный недостаток довольно просто!
// Фактически мы реализуем композицию выражений,
// которая даст нам выражение, соответствующее композиции целевых функций
public static Expression<Func<TIn, TOut>> Compose<TIn, TInOut, TOut>(
this Expression<Func<TIn, TInOut>> input
, Expression<Func<TInOut, TOut>> 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<Func<TIn, TOut>>(res, param);
}
// Добавляем "продвинутый" вариант Where
public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable
, Expression<Func<T, TParam>> prop
, Expression<Func<TParam, bool>> 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);
}
Спасибо за внимание!
Автор: marshinov