Страницы

Поиск по вопросам

четверг, 16 мая 2019 г.

Как составить дерево выражения для лямбды?

Контекст — этот вопрос: Сортировка по DayOfWeek, неделя начинается с понедельника
Хочется отрефакторить код сортировки и вместо непонятной лямбды
s => ((int)s.DayOfWeek + 6) % 7
написать что-то вроде
s => GetDayNumberInWeek(s.DayOfWeek, weekStartsWith: DayOfWeek.Monday)
Выясняется, что деревья выражений не поддерживают именованные параметры, т.е. придется писать менее понятно:
s => GetDayNumberInWeek(s.DayOfWeek, DayOfWeek.Monday)
Пусть так, но как это сделать? Если написать
static int GetDayNumberInWeek(DayOfWeek dayOfWeek, DayOfWeek weekStartsWith) { int daysInWeek = 7; return ((int)dayOfWeek + daysInWeek - (int)weekStartsWith) % daysInWeek; }
То сортировка, понятно, начнет выполняться на клиентской стороне, а этого не хочется.
Если я возвращаю из метода любой Expression, то EF ругается на отсутствие реализации IComparable
Есть ли вообще решение у такой задачи?


Ответ

Вам нужно генерировать всю лямбду целиком, а не только правую часть.
На более простом примере, чтобы не шумело создание Body:
Пусть у вас есть:
context.Schedules .OrderBy(s => s.DayOfWeek) .ToList();
И вы хотите строить key selector для динамически:
Выносим селектор целиком:
context.Schedules .OrderBy(BuildKeySelector()) .ToList();
private static Expression> BuildKeySelector() { return s => s.DayOfWeek; }
И разворачиваем сгенерированный код:
context.Schedules .OrderBy(BuildDayOfWeekSelector()) .ToList();
private static Expression> BuildDayOfWeekSelector() { var parameter = Expression.Parameter(typeof(Schedule), "s"); // левая часть
// правая часть, тут должно быть более сложное дерево var body = Expression.MakeMemberAccess( parameter, typeof(Schedule).GetProperty("DayOfWeek"));
var lambda = Expression.Lambda>(body, parameter);
return lambda; }
Если надо отвязать BuildDayOfWeekSelector от Schedule - просто сделайте его Generic (но придется указывать тип при вызове).
private static Expression> BuildDayOfWeekSelector() { var parameter = Expression.Parameter(typeof(T), "s"); // левая часть
var body = Expression.MakeMemberAccess( // правая часть parameter, typeof(T).GetProperty("DayOfWeek"));
var lambda = Expression.Lambda>(body, parameter);
return lambda; }
Остаток динамики - параметр weekStartsWith - спускайте параметром в BuildKeySelector и используйте при построении body как Expression.Constant(weekStartsWith);
Точное дерево, которое надо строить руками, можно легко подсмотреть - сделать extract local для сгенерированной компилятором лямбды, и развернуть ее в отладчике.

Комментариев нет:

Отправить комментарий