#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();
Комментариев нет:
Отправить комментарий