Объединение двух выражений (выражение <Func >)
У меня есть два выражения типа Expression<Func>
и я хочу взять OR, AND или NOT из них и получить новое выражение того же типа
Expression<Func> expr1; Expression<Func> expr2; ... //how to do this (the code below will obviously not work) Expression<Func> andExpression = expr AND expr2
- Операция может дестабилизировать время выполнения?
- Пересечение нескольких списков с помощью IEnumerable.Intersect ()
- Linq to EntityFramework DateTime
- LINQ: использование INNER JOIN, Group и SUM
- Легкий способ преобразования словаря в xml и наоборот
- Макс или по умолчанию?
- CSV для сопоставления объектной модели
- System.LINQ.Dynamic: выберите («new (...)») в List (или любую другую перечислимую коллекцию )
- Невозможно создать постоянное значение - только примитивные типы
- Linq - максимальное значение для каждой группы
- Почему LINQ .Where (предикат) .First () быстрее, чем .First (предикат)?
- Синтаксис запроса .NET LINQ и цепочка методов
- В чем разница между IQueryable и IEnumerable
Ну, вы можете использовать Expression.AndAlso
/ OrElse
т. Д. Для объединения логических выражений, но проблема заключается в параметрах; вы работаете с тем же ParameterExpression
в expr1 и expr2? Если так, то проще:
var body = Expression.AndAlso(expr1.Body, expr2.Body); var lambda = Expression.Lambda>(body, expr1.Parameters[0]);
Это также хорошо работает, чтобы свести на нет одну операцию:
static Expression> Not( this Expression> expr) { return Expression.Lambda>( Expression.Not(expr.Body), expr.Parameters[0]); }
В противном случае, в зависимости от поставщика LINQ, вы можете комбинировать их с Invoke
:
// OrElse is very similar... static Expression> AndAlso( this Expression> left, Expression> right) { var param = Expression.Parameter(typeof(T), "x"); var body = Expression.AndAlso( Expression.Invoke(left, param), Expression.Invoke(right, param) ); var lambda = Expression.Lambda>(body, param); return lambda; }
Где-то у меня есть код, который переписывает дерево выражений, заменяя узлы, чтобы удалить необходимость Invoke
, но он довольно длинный (и я не могу вспомнить, где я его оставил).
Обобщенная версия, которая выбирает самый простой маршрут:
static Expression> AndAlso( this Expression> expr1, Expression> expr2) { // need to detect whether they use the same // parameter instance; if not, they need fixing ParameterExpression param = expr1.Parameters[0]; if (ReferenceEquals(param, expr2.Parameters[0])) { // simple version return Expression.Lambda>( Expression.AndAlso(expr1.Body, expr2.Body), param); } // otherwise, keep expr1 "as is" and invoke expr2 return Expression.Lambda>( Expression.AndAlso( expr1.Body, Expression.Invoke(expr2, param)), param); }
Начиная с .net 4.0. Существует class ExpressionVistor, который позволяет создавать выражения, безопасные для EF.
public static Expression> AndAlso( this Expression> expr1, Expression> expr2) { var parameter = Expression.Parameter(typeof (T)); var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); var left = leftVisitor.Visit(expr1.Body); var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); var right = rightVisitor.Visit(expr2.Body); return Expression.Lambda>( Expression.AndAlso(left, right), parameter); } private class ReplaceExpressionVisitor : ExpressionVisitor { private readonly Expression _oldValue; private readonly Expression _newValue; public ReplaceExpressionVisitor(Expression oldValue, Expression newValue) { _oldValue = oldValue; _newValue = newValue; } public override Expression Visit(Expression node) { if (node == _oldValue) return _newValue; return base.Visit(node); } }
Вы можете использовать Expression.AndAlso / OrElse для объединения логических выражений, но вы должны убедиться, что ParameterExpressions одинаковы.
У меня были проблемы с EF и PredicateBuilder, поэтому я сделал свой собственный, не прибегая к Invoke, что я мог бы использовать вот так:
var filterC = filterA.And(filterb);
Исходный код моего PredicateBuilder:
public static class PredicateBuilder { public static Expression> And(this Expression> a, Expression> b) { ParameterExpression p = a.Parameters[0]; SubstExpressionVisitor visitor = new SubstExpressionVisitor(); visitor.subst[b.Parameters[0]] = p; Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body)); return Expression.Lambda>(body, p); } public static Expression> Or (this Expression> a, Expression> b) { ParameterExpression p = a.Parameters[0]; SubstExpressionVisitor visitor = new SubstExpressionVisitor(); visitor.subst[b.Parameters[0]] = p; Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body)); return Expression.Lambda>(body, p); } }
И class утилиты для замены параметров в lambda:
internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor { public Dictionary subst = new Dictionary(); protected override Expression VisitParameter(ParameterExpression node) { Expression newValue; if (subst.TryGetValue(node, out newValue)) { return newValue; } return node; } }
Джо Альбахари (автор C # 3.0 в двух словах и LINQPad) написал утилиту PredicateBuilder, которая может использоваться для функций AND и OR вместе.
http://www.albahari.com/nutshell/predicatebuilder.aspx
Хотя он работает над функциями, он является открытым исходным кодом, поэтому вы можете проверить его и посмотреть, как он работает.
Если поставщик не поддерживает Invoke, и вам нужно объединить два выражения, вы можете использовать ExpressionVisitor для замены параметра во втором выражении параметром в первом выражении.
class ParameterUpdateVisitor : ExpressionVisitor { private ParameterExpression _oldParameter; private ParameterExpression _newParameter; public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter) { _oldParameter = oldParameter; _newParameter = newParameter; } protected override Expression VisitParameter(ParameterExpression node) { if (object.ReferenceEquals(node, _oldParameter)) return _newParameter; return base.VisitParameter(node); } } static Expression> UpdateParameter( Expression> expr, ParameterExpression newParameter) { var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter); var body = visitor.Visit(expr.Body); return Expression.Lambda>(body, newParameter); } [TestMethod] public void ExpressionText() { string text = "test"; Expression> expr1 = p => p.Item1.Contains(text); Expression> expr2 = q => q.Item2.Contains(text); Expression> expr3 = UpdateParameter(expr2, expr1.Parameters[0]); var expr4 = Expression.Lambda>( Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]); var func = expr4.Compile(); Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" })); }
Я предлагаю еще одно усовершенствование решений PredicateBuilder и ExpressionVisitor
. Я назвал его UnifyParametersByName
и вы можете найти его в моей MIT-библиотеке: LinqExprHelper . Это позволяет комбинировать произвольные lambda-выражения. Обычно задаются вопросы о предикатном выражении, но эта идея распространяется и на проекционные выражения.
В следующем коде используется метод ExprAdres
который создает сложное параметризованное выражение, используя встроенную lambda. Это сложное выражение кодируется только один раз, а затем повторно используется благодаря мини-библиотеке LinqExprHelper
.
public IQueryable UbezpFull { get { System.Linq.Expressions.Expression< Func> expr = (u, parAdrM, parAdrZ) => new UbezpExt { Ub = u, AdrM = parAdrM, AdrZ = parAdrZ, }; // From here an expression builder ExprAdres is called. var expr2 = expr .ReplacePar("parAdrM", ExprAdres("M").Body) .ReplacePar("parAdrZ", ExprAdres("Z").Body); return UBEZPIECZONY.Select((Expression>)expr2); } }
И это код построения подвыражения:
public static Expression> ExprAdres(string sTyp) { return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp) .OrderByDescending(a => a.DATAOD).FirstOrDefault(); }
То, что я пытался достичь, – это выполнить параметризованные запросы без необходимости скопировать-вставить и с возможностью использования встроенных lambdas, которые так хороши. Без всех этих вспомогательных выражений я был бы вынужден создать цельный запрос за один раз.
Мне нужно было добиться тех же результатов, но использовать что-то более общее (поскольку тип не был известен). Благодаря ответу Марка я наконец понял, чего я пытаюсь достичь:
public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp) { var parameter = Expression.Parameter(sourceType); var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter); var left = leftVisitor.Visit(exp.Body); var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter); var right = rightVisitor.Visit(newExp.Body); var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool)); return Expression.Lambda(delegateType, Expression.Or(left, right), parameter); }
Я думаю, это прекрасно работает, не так ли?
Func expr1 = (x => x.Att1 == "a"); Func expr2 = (x => x.Att2 == "b"); Func expr1ANDexpr2 = (x => expr1(x) && expr2(x)); Func expr1ORexpr2 = (x => expr1(x) || expr2(x)); Func NOTexpr1 = (x => !expr1(x));