Динамическая сортировка LINQ: как применить OrderBy к IEnumerable/IQueryable по имени свойства из строки?
Решение с помощью System.Linq.Dynamic.Core
Самый простой и рекомендуемый способ — использовать библиотеку System.Linq.Dynamic.Core. Она добавляет расширения для LINQ, позволяющие передавать имена свойств и выражения сортировки в виде строк. Это избавляет от необходимости писать громоздкие конструкции с рефлексией.
Установка
Через NuGet Package Manager:
Install-Package System.Linq.Dynamic.Core
Или через .NET CLI:
dotnet add package System.Linq.Dynamic.Core
Использование
После установки библиотеки вы можете использовать метод OrderBy, который принимает строку с именем свойства и направлением сортировки.
using System.Linq.Dynamic.Core;
public List<Person> SortPeople(IEnumerable<Person> people, string sortBy, bool ascending)
{
var query = people.AsQueryable();
// Формируем строку сортировки: "Name ascending" или "Age descending"
string direction = ascending ? "ascending" : "descending";
string orderByString = $"{sortBy} {direction}";
// Применяем динамическую сортировку
return query.OrderBy(orderByString).ToList();
}
Как это работает
Метод OrderBy(string) из библиотеки Dynamic.Core парсит переданную строку, находит свойство с указанным именем через рефлексию (но делает это эффективно, с кэшированием) и строит соответствующее дерево выражений. Это даёт чистый и читаемый код без множественных if-else.
Поддержка множественной сортировки
Вы также можете сортировать по нескольким полям, разделяя их запятыми:
// Сначала по "Name" по возрастанию, затем по "Age" по убыванию
var sorted = query.OrderBy("Name ascending, Age descending").ToList();
Важные замечания
- IQueryable vs IEnumerable: Если ваша коллекция — это
IQueryable(например, из Entity Framework), сортировка будет выполняться на стороне базы данных. ДляIEnumerableона выполняется в памяти. В примере выше мы явно преобразуемIEnumerableвIQueryableчерезAsQueryable(), чтобы использовать динамический метод. - Безопасность: Не передавайте пользовательский ввод напрямую без валидации, чтобы избежать возможных ошибок. Проверяйте, что свойство с таким именем действительно существует в классе.
- Производительность: Для
IQueryableбиблиотека генерирует правильный SQL-запрос, поэтому производительность будет нативной. ДляIEnumerableесть небольшой оверхед на рефлексию, но он обычно незаметен.
Альтернатива: ручное построение Expression
Если вы не хотите использовать стороннюю библиотеку, можно построить дерево выражений вручную. Это сложнее, но даёт полный контроль:
public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, string propertyName, bool ascending)
{
var type = typeof(T);
var property = type.GetProperty(propertyName);
if (property == null)
throw new ArgumentException($"Property '{propertyName}' not found on type '{type.Name}'");
var parameter = Expression.Parameter(type, "x");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var methodName = ascending ? "OrderBy" : "OrderByDescending";
var resultExp = Expression.Call(
typeof(Queryable),
methodName,
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
Использование:
var sorted = people.AsQueryable().OrderByDynamic("Name", ascending: true).ToList();
Этот метод можно оформить как extension-метод для IQueryable.
Итог: Для 90% случаев достаточно библиотеки System.Linq.Dynamic.Core. Она проста, надёжна и широко используется. Ручное построение выражений оправдано только в особых случаях, когда нужно избежать внешних зависимостей.
Альтернативный подход: использование рефлексии и Expression Trees вручную
Если вы не хотите подключать внешнюю библиотеку, можно реализовать динамическую сортировку через рефлексию и деревья выражений. Этот метод даёт полный контроль и не требует дополнительных зависимостей.
Реализация для IEnumerable
public static IEnumerable<T> OrderByDynamic<T>(this IEnumerable<T> source, string propertyName, bool ascending)
{
var param = Expression.Parameter(typeof(T), "item");
var property = Expression.PropertyOrField(param, propertyName);
var sortLambda = Expression.Lambda(property, param);
var expressionType = typeof(Enumerable);
var methodName = ascending ? "OrderBy" : "OrderByDescending";
var method = expressionType.GetMethods()
.First(m => m.Name == methodName && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), property.Type);
return (IEnumerable<T>)method.Invoke(null, new object[] { source, sortLambda.Compile() });
}
Использование:
var sortedPeople = people.OrderByDynamic("Age", ascending: false).ToList();
Плюсы и минусы
- Плюсы: Нет внешних зависимостей; полный контроль над процессом; можно легко модифицировать (например, добавить поддержку вложенных свойств).
- Минусы: Код сложнее для понимания; рефлексия медленнее, чем специализированная библиотека; необходимо обрабатывать ошибки (если свойство не найдено или тип не поддерживает сравнение).
Этот подход хорошо подходит для небольших проектов или случаев, когда количество сортируемых полей ограничено и не меняется часто.
Ответов пока нет.
Войдите чтобы оставить ответ