Подтвердить что ты не робот

Как объединить выражения LINQ в один?

У меня есть форма с несколькими полями на ней (название компании, почтовый индекс и т.д.), которая позволяет пользователю искать компании в базе данных. Если пользователь вводит значения в более чем одном поле, мне нужно выполнить поиск по всем этим полям. Я использую LINQ для запроса базы данных.

До сих пор мне удалось написать функцию, которая будет смотреть на их вклад и превратить ее в список выражений. Теперь я хочу превратить этот список в одно выражение, которое затем я могу выполнить через поставщика LINQ.

Моя первоначальная попытка была следующей:

private Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions)
    {
        if (expressions.Count == 0)
        {
            return null;
        }
        if (expressions.Count == 1)
        {
            return expressions[0];
        }
        Expression<Func<Company, bool>> combined = expressions[0];
        expressions.Skip(1).ToList().ForEach(expr => combined = Expression.And(combined, expr));
        return combined;
    }

Однако это не удается с сообщением об исключении в строках "Бинарный оператор И не определен для...". Кто-нибудь есть идеи, что мне нужно сделать, чтобы объединить эти выражения?

ИЗМЕНИТЬ: Исправлена ​​строка, в которой я забыл присвоить результат и преобразовать выражения вместе с переменной. Спасибо, что указали, что люди.

4b9b3361

Ответ 1

EDIT: ответ Джейсона теперь более полный, чем мой, с точки зрения дерева выражений, поэтому я удалил этот бит. Однако я хотел оставить это:

Я предполагаю, что вы используете их для предложения Where... почему бы просто не вызвать Where с каждым выражением в свою очередь? Это должно иметь тот же эффект:

var query = ...;
foreach (var condition in conditions)
{
    query = query.Where(condition);
}

Ответ 2

Вы можете использовать Enumerable.Aggregate в сочетании с Expression.AndAlso. Вот общая версия:

Expression<Func<T, bool>> AndAll<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions) {

    if(expressions == null) {
        throw new ArgumentNullException("expressions");
    }
    if(expressions.Count() == 0) {
        return t => true;
    }
    Type delegateType = typeof(Func<,>)
                            .GetGenericTypeDefinition()
                            .MakeGenericType(new[] {
                                typeof(T),
                                typeof(bool) 
                            }
                        );
    var combined = expressions
                       .Cast<Expression>()
                       .Aggregate((e1, e2) => Expression.AndAlso(e1, e2));
    return (Expression<Func<T,bool>>)Expression.Lambda(delegateType, combined);
}

Ваш текущий код никогда не присваивает combined:

expr => Expression.And(combined, expr);

возвращает новый Expression, который является результатом побитового и combined и expr, но не мутирует combined.

Ответ 3

Здесь у нас есть общий вопрос о объединении выражений Linq. У меня есть общее решение этой проблемы. Я дам ответ на конкретную проблему, хотя это определенно не способ пойти в таких случаях. Но когда простые решения терпят неудачу в вашем случае, вы можете попытаться использовать этот подход.

Сначала вам нужна библиотека, состоящая из 2 простых функций. Они используют System.Linq.Expressions.ExpressionVisitor для динамического изменения выражений. Ключевой особенностью является унификация параметров внутри выражения, так что 2 параметра с тем же именем были сделаны идентичными (UnifyParametersByName). Оставшаяся часть заменяет именованный параметр заданным выражением (ReplacePar). Библиотека доступна с лицензией MIT на github: LinqExprHelper, но вы можете быстро написать что-то самостоятельно.

Библиотека позволяет довольно простой синтаксис для комбинирования сложных выражений. Вы можете смешивать встроенные лямбда-выражения, которые приятно читать, вместе с созданием динамических выражений и композицией, которые очень способны.

    private static Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions)
    {
        if (expressions.Count == 0)
        {
            return null;
        }

        // Prepare a master expression, used to combine other
        // expressions. It needs more input parameters, they will
        // be reduced later.
        // There is a small inconvenience here: you have to use
        // the same name "c" for the parameter in your input
        // expressions. But it may be all done in a smarter way.
        Expression <Func<Company, bool, bool, bool>> combiningExpr =
            (c, expr1, expr2) => expr1 && expr2;

        LambdaExpression combined = expressions[0];
        foreach (var expr in expressions.Skip(1))
        {
            // ReplacePar comes from the library, it an extension
            // requiring `using LinqExprHelper`.
            combined = combiningExpr
                .ReplacePar("expr1", combined.Body)
                .ReplacePar("expr2", expr.Body);
        }
        return (Expression<Func<Company, bool>>)combined;
    }