Как работает PredicateBuilder

C# в Nutshell имеет бесплатный класс PredicateBuilder, который строит предикаты LINQ по частям, доступные здесь. Вот выдержка из метода, который добавляет новое выражение к предикату. Может кто-нибудь объяснить это? (Я видел этот вопрос, мне не нужен общий ответ, как там. Я ищу конкретное объяснение того, как Expression.Invoke и Expression.Lambda строят новое выражение).

public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                     Expression<Func<T, bool>> expr2)
{
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
        (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}

1 ответ

Решение

Допустим, у вас есть:

Expression<Func<Person, bool>> isAdult = p1 => p1.Age >= 18;

// I've given the parameter a different name to allow you to differentiate.
Expression<Func<Person, bool>> isMale = p2 => p2.Gender == "Male";

А затем объединить их с PredicateBuilder

var isAdultMale = isAdult.And(isMale);

Какие PredicateBuilder Производит это выражение, которое выглядит так:

// Invoke has no direct equivalent in C# lambda expressions.
p1 => p1.Age >= 18 && Invoke(p2 => p2.Gender == "Male", p1)

Как вы видете:

  1. Результирующая лямбда повторно использует параметры первого выражения.
  2. Имеет тело, которое вызывает второе выражение, передавая параметры первого выражения в качестве замены параметров второго выражения. Результирующий InvocationExpression вроде как выражение-эквивалент вызова метода (вызов подпрограммы путем передачи аргументов для параметров).
  3. And тело первого выражения и это InvocationExpression вместе, чтобы произвести тело получающейся лямбды.

Идея заключается в том, что поставщик LINQ должен уметь понимать семантику этой операции и предпринимать разумные действия (например, генерировать SQL как WHERE age >= 18 AND gender = 'Male').

Однако часто у провайдеров возникают проблемы с InvocationExpression s, из-за очевидных сложностей обработки "вложенного вызова-выражения внутри выражения".

Чтобы обойти это, LINQKit также предоставляет Expand помощник. Это, по сути, "встраивает" вызов вызова в умное состояние, заменяя вызов телом вложенного выражения, соответственно заменяя использование параметров вложенного выражения (в данном случае заменяя p2 с p1). Это должно произвести что-то вроде:

p1 => p1.Age >= 18 && p1.Gender == "Male"

Обратите внимание, что таким образом вы бы вручную объединили эти предикаты, если бы сделали это сами в лямбда-выражении. Но с помощью LINQKit вы можете получить эти предикаты из независимых источников и легко объединить их:

  1. Без написания "вручную" кода выражения.
  2. Необязательно, таким образом, чтобы это было прозрачно для потребителей получаемой лямбды.
Другие вопросы по тегам