Страницы

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

воскресенье, 1 марта 2020 г.

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

#c_sharp #linq #деревья_выражений


Контекст — этот вопрос: Сортировка по 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.

Есть ли вообще решение у такой задачи?
    


Ответы

Ответ 1



Вам нужно генерировать всю лямбду целиком, а не только правую часть. На более простом примере, чтобы не шумело создание 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 для сгенерированной компилятором лямбды, и развернуть ее в отладчике.

Ответ 2



Всё получилось: static Expression> BuildDayIndexInWeekSelector( Expression> dayOfWeekSelector, DayOfWeek weekStartsWith) { int daysInWeek = 7; var parameterExp = dayOfWeekSelector.Parameters[0]; var daysShiftExp = Expression.Constant(daysInWeek - (int)weekStartsWith); var daysInWeekExp = Expression.Constant(daysInWeek); var dayOfWeekExp = Expression.Convert( dayOfWeekSelector.Body, typeof(int)); var bodyExp = Expression.Modulo( Expression.Add( dayOfWeekExp, daysShiftExp), daysInWeekExp); var lambdaExp = Expression.Lambda>( bodyExp, parameterExp); return lambdaExp; } Используется так: var schedules = db.Schedules .OrderBy( BuildDayIndexInWeekSelector( s => s.DayOfWeek, weekStartsWith: DayOfWeek.Monday)) .ToList();

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

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