Объединение двух выражений (выражение <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 

Ну, вы можете использовать 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)); 
  • LINQ: динамический выбор
  • LINQ Distinct () для определенного свойства
  • Используйте собственный IComparer с Linq OrderBy
  • Порядок LINQ по нулевому столбцу, где порядок возрастает, а null должны быть последними
  • Невозможно неявно преобразовать тип 'System.Collections.Generic.IEnumerable ' в 'System.Collections.Generic.List
  • Linq с группой с подсчетом
  • LINQ для нескольких баз данных
  • Выберите выделение, используя linq
  • Проверьте, равны ли два списка
  • LINQ, Невозможно создать постоянное значение типа XXX. В этом контексте поддерживаются только примитивные типы или типы перечислений
  • LINQ: когда использовать SingleOrDefault и FirstOrDefault () с критериями фильтрации
  • Interesting Posts

    У меня есть окна 7 os, установленные в виртуальном ящике. Я хочу установить программное обеспечение в этом os. Как это сделать?

    Каковы различия между обычным и тонким пакетом jquery?

    Google Chrome полностью не отвечает

    Включение зависимостей в фильтры действия ASP.NET MVC 3. Что не так с этим подходом?

    Должен ли я установить Windows 8 Developer Preview поверх Windows 7?

    Как найти неиспользуемый / мертвый код в java-проектах

    есть ли обратная связь для рендеринга для Angular JS?

    Тест-селектор артефактов отсутствует / ушел из вариантов сборки в Android Studio 2 Beta 5

    Есть ли способ добавить пользовательский значок к вкладке «Приложение»?

    Публикация в список MVC3

    Как записывать как микрофон, так и динамик в Windows 7

    Как подавлять предупреждения в глобальном масштабе в R Script

    Есть ли какая-либо польза для подключения устройства USB 2.0 к порту USB 3.0?

    Получение растрового изображения из векторного рисунка

    Почему полезен указатель «точка-volatile», например «volatile int * p»?

    Давайте будем гением компьютера.