Alex-1978 Ответов: 2

Получите доступ к параметру родительского выражения в дереве выражений.


Я работаю с Entity Framework, и мне нужно динамически создавать фильтры во время выполнения.
Мне нужно создать выражение, которое должно иметь возможность доступа к параметру родительского вызова, например:
х =&ГТ; х.Роли.Выберите(y => y.действия.Где(з =&ГТ; !х.Метод isactive || з.Метод isactive))

так что этот фильтр должен выбрать _все_ действия для корневого объекта, если это корневой объект-это _не_ активный, или выбрать все действия _active_, если объект является активным.

У меня есть код, который создает выражение такого рода, но не ясно, как получить доступ к параметру x из фильтра Where.
Поскольку фильтр Where представлен отдельным выражением, которое принимает только параметр z, он не имеет привязки X.


Что нужно сделать, чтобы этот параметр был доступен на этом уровне ?

Спасибо,
Алекс

Что я уже пробовал:

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

Nathan Minier

Я думаю, что у вас есть более фундаментальная проблема, если ваш дизайн позволяет контейнеру ролей предоставлять "активное" переопределение конкретным действиям. Свойства должны иметь значение; возможно, будет полезно пересмотреть структуру.

Во всяком случае, функциональность, которую вы ищете, лучше обслуживается вложенными циклами, а не деревом выражений.

Alex-1978

Спасибо за ваш комментарий.
Не могли бы Вы уточнить, о каких вложенных циклах идет речь ?

Gerry Schmitz

Насколько я могу судить, "x" находится "в области видимости" (или это то, что говорят мне мои тесты).

2 Ответов

Рейтинг:
18

Richard Deeming

Внутреннее лямбда-выражение будет иметь доступ к параметрам из внешнего лямбда-выражения:

var x = Expression.Parameter(typeof(Widget), "x");
var y = Expression.Parameter(typeof(Role), "y");
var z = Expression.Parameter(typeof(RoleAction), "z");

// z => !x.IsActive || z.IsActive
var roleActionBody = Expression.OrElse(
    Expression.Not(Expression.Property(x, "IsActive")),
    Expression.Property(z, "IsActive"));

// y => y.Actions.Where(z => ...)
var roleBody = Expression.Call(typeof(Enumerable), "Where", new[] { typeof(RoleAction) },
    Expression.Property(y, "Actions"),
    Expression.Lambda(roleActionBody, z));

// x => x.Roles.Select(y => ...)
var body = Expression.Call(typeof(Enumerable), "Select", new[] { typeof(Role), typeof(IEnumerable<RoleAction>) },
    Expression.Property(x, "Roles"),
    Expression.Lambda(roleBody, y));

var result = Expression.Lambda<Func<Widget, IEnumerable<IEnumerable<RoleAction>>>>(body, x);

NB: В результате получается последовательность последовательностей RoleAction объекты. Если вы хотите свести это к последовательности RoleAction объекты, которые вам нужно будет использовать SelectMany:
...

// x => x.Roles.SelectMany(y => ...)
var body = Expression.Call(typeof(Enumerable), "SelectMany", new[] { typeof(Role), typeof(RoleAction) },
    Expression.Property(x, "Roles"),
    Expression.Lambda(roleBody, y));

var result = Expression.Lambda<Func<Widget, IEnumerable<RoleAction>>>(body, x);


Alex-1978

Спасибо за ваш комментарий! Это дало мне очень важный совет.

Просто нужно было добавить замену параметра 'x' в теле roleActionBody.
Я только произвел замену в параметрах этого выражения.

Я новичок в дереве выражений, поэтому для меня это было не очевидно :)

Большое спасибо!

Рейтинг:
0

Gerry Schmitz

"x" находится в области видимости.

class RoleAction {
   public bool IsActive;
}

class Role {
   public List<RoleAction> Actions = new List<RoleAction>();
}

class Widget {
   public List<Role> Roles = new List<Role>();
   public bool IsActive;
}

class Program {
   static void Main( string[] args ) {
      List<Widget> widgets = new List<Widget>();
      var q = widgets.Select( x => x.Roles.Select( y => y.Actions.Where( z => !x.IsActive || z.IsActive ) ) );
   }
}


Alex-1978

Да, именно это я и пытаюсь сделать во время выполнения, используя класс Expression.
Я создаю выражения для каждого шага:

Выражение&ЛТ;изм&ЛТ;действия, типа bool&ГТ;&ГТ; выражение1 = З =И gt; !х.Метод isactive || з.Метод isactive;
Выражение&ЛТ;изм&ЛТ;роль, интерфейс IEnumerable&ЛТ;действий&ГТ;&ГТ;&ГТ; выражение2 = г =&ГТ; г.Действия.Где(...)
Выражение&ЛТ;изм&ЛТ;виджет, интерфейс IEnumerable&ЛТ;действий&ГТ;&ГТ;&ГТ; выражение3 =&ГТ; х =&ГТ; х.Роли ...

но этот код не пишется непосредственно на C#, он создается на лету, и проблема заключается в том, что параметр 'x' из expr3 не привязан к контексту вызова expr1.
Я использую ExpressionVisitor для замены параметров в конечном выражении, но поскольку expr1 не принимает " x " в качестве параметра, у него нет доступа к нему.

Вопрос действительно в том, какой механизм должен быть использован для передачи 'x' в expr1 ?
Может быть, это конец ? Если да, то можно ли создать clouse для узла в дереве выражений ?
До сих пор я не нашел никакой информации об этом.

Gerry Schmitz

Func<bool> GetX() { return !x.IsActive; // или что-то еще. }

Выражение&ЛТ;изм&ЛТ;действия, типа bool&ГТ;&ГТ; выражение1 = З =И gt; GetX() || з.Метод isactive;

(Опять же, вам следует пересмотреть оценку "родительского" свойства на дочернем уровне, если речь идет о производительности, поскольку "выбрать все" иногда будет более уместно).