Динамический LINQ. Окончание
Начало
2. OrderByDynamic/ThenByDynamic
Даём пользователю возможность отсортировать результаты по любой колонке (из белого списка).
var sort = sortCol switch
{
"LastLogon" => "x => x.LastLogon",
"TotalSpent" => "x => x.TotalSpent",
_ => "x => x.Name"
};
var ordered = await context.Customers
.OrderByDynamic(sort)
.ThenByDynamic("x => x.Id")
.ToListAsync();
3. SelectDynamic
Для сценариев отчётов/экспорта, выдаём только то, что запросил клиент:
// Клиент выбрал: "Id,Name,Country"
var cols = selectedCols.Split(',')
.Select(c => c.Trim())
.Where(c => allowedCols.Contains(c));
var selectExpr = "x => new { "
+ string.Join(", ", cols.Select(c => $"{c} = x.{c}"))
+ " }";
var result = await context.Customers
.WhereDynamic("x => x.Status == 0")
.SelectDynamic(selectExpr)
.ToListAsync();
4. FirstOrDefaultDynamic
Идеален для поиска одного элемента или валидации на основе критерия, определяющегося во время выполнения:
csharp
var result = await context.Customers
.FirstOrDefaultDynamic(
"x => x.Email == \\\"name@mail.com\\\" && x.Status == 0");
Бонус: Цепочки динамических конвейеров
Если очень нужно выполнить несколько шагов LINQ в одной динамической строке (фильтрация → упорядочивание → выборка → список), есть API Execute:
var env = new {
IsActive = CustomerStatus.IsActive,
LastMonth = DateTime.Now.AddMonths(-1) };
var result = context.Customers.Execute<IEnumerable>(
"Where(x => x.Status == IsActive && x.LastLogon >= LastMonth)" +
".Select(x => new { x.CustomerID, x.Name })" +
".OrderBy(x => x.CustomerID).ToList()", env);Это мощный метод, но код получается не особо читаемый.
Варианты использования
1. Администрирование «Конструктор запросов»
- UI генерирует: поле + оператор + значение;
- Бэкенд выбирает разрешённые поля/операции → строит WhereDynamic (и опционально OrderByDynamic);
- Результат: один конвейер запросов, почти бесконечное количество комбинаций.
2. Маркетинг «Конструктор сегментов»
- Сегменты сохраняются как читаемые выражения (например, «Активные, последние 90 дней,>$500,не тестовые аккаунты»);
- Приложение загружает правило → WhereDynamic → сохраняет результаты;
- Результат: правила развиваются без изменения кода или повторного развёртывания.
3. Многопользовательские правила
- Каждый пользователь хранит несколько предикатов (или базовых фильтров разрешения/запрета);
- Они комбинируются во время запроса и применяются динамически;
- Результат: меньше ветвлений/флагов, более чистая модель.
Замечания
- Белый список полей/операторов: не раскрывайте всю модель; предоставьте в UI только разрешённый список.
- Проверка выражений: отклоняйте неизвестные токены/поля перед выполнением.
- IQueryable до конца: применяйте динамические операции перед ToList(), чтобы EF мог преобразовать их в SQL.
- Нормализуйте значения: используйте даты ISO или параметры (объект env) вместо парсинга текста.
- Сохраняйте читаемость: предпочитайте короткие, компонуемые строки; добавляйте вспомогательные методы для частых случаев.
Когда не использовать
Если у вас 2-3 фиксированных фильтра, которые редко меняются, статический LINQ остается самым простым (и вполне приемлемым). Динамический LINQ имеет смысл при росте изменчивости и опциональности.
Источник: https://thecodeman.net/posts/dynamic-linq-in-dotnet