Как объединить выражения 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;
}
Однако это не удается с сообщением об исключении в строках "Бинарный оператор И не определен для...". Кто-нибудь есть идеи, что мне нужно сделать, чтобы объединить эти выражения?
ИЗМЕНИТЬ: Исправлена строка, в которой я забыл присвоить результат и преобразовать выражения вместе с переменной. Спасибо, что указали, что люди.
Ответы
Ответ 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;
}