Мне потребовалось некоторое время, чтобы понять, что вы используете Union
в попытке упорядочить данные по «силе совпадения»: сначала те, которые точно соответствуют, затем те, которые соответствуют другому регистру и т. Д. Когда я вижу Union
s с предикатами мой обусловленный Павловым ум переводит его в OR
s.Мне пришлось переключиться с быстро думать на медленный .
Так что проблема в том, что нет предсказуемой сортировки.Без сомнения, цепочечные операторы Union
действительно создают детерминированный конечный порядок сортировки, но это не обязательно порядок Union
с, потому что каждый Union
также выполняет неявный Distinct
.Общее правило: если вам нужен определенный порядок сортировки, используйте OrderBy
методы.
Сказав это, и взяв ...
var result = entities
.Where(e => e.Name.Contains(searchTerm))
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();
... желаемый результат кажется доступным для:
var sortedEntities = result
.OrderByDescending(e => e.Name == searchTerm)
.ThenByDescending(e => e.Name.ToLower() == searchTerm.ToLower())
.ThenByDescending(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))
... etc.
(по убыванию, потому что false
orders before
true)
Однако, если совпадений больше, чем pageSize
заказ будет слишком поздно.Если pageSize = 20
и пункт 21 является первым точным соответствием, этот элемент не будет отображаться на странице 1. Это означает, что заказ должен быть выполнен до подкачки.
Первым шагом будетудалите .ToList()
из первого оператора.Если вы удалите его, первый оператор будет выражением IQueryable
, и Entity Framework сможет объединить полный оператор в один оператор SQL.Следующим шагом будет перемещение Skip/Take
в конец полного оператора, и он также будет частью SQL.
var result = entities.Where(e => e.Name.Contains(searchTerm));
var sortedEntities = result
.OrderByDescending(e => e.Name == searchTerm)
.ThenByDescending(e => e.Name.ToLower() == searchTerm.ToLower())
.ThenByDescending(e => e.Name.StartsWith(searchTerm, StringComparison.OrdinalIgnoreCase))
... etc
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();
Но теперь возникает новая проблема.
Поскольку сравнение строк с StringComparison.OrdinalIgnoreCase
не поддерживается, Entity Framework автоматически переключится на оценку на стороне клиента для части оператора.Все отфильтрованные результаты будут возвращены из базы данных, но большая часть упорядочения и вся подкачка будут выполняться в памяти.
Это может быть не так уж плохо, когда фильтр узок, но очень плохокогда он широкийТак что, в конечном счете, чтобы сделать это правильно, вы должны удалить StringComparison.OrdinalIgnoreCase
и рассчитаться с несколько меньшей уточненной силой матча.Приводит нас к
Конечному результату :
var result = entities.Where(e => e.Name.Contains(searchTerm));
var sortedEntities = result
.OrderByDescending(e => e.Name == searchTerm)
.ThenByDescending(e => e.Name.StartsWith(searchTerm))
.ThenByDescending(e => e.Name.Contains($" {searchTerm} "))
.ThenByDescending(e => e.Name.EndsWith(searchTerm))
.ThenByDescending(e => e.Name.Contains(searchTerm))
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize).ToList();
Почему «менее изысканный»?Поскольку, согласно вашим комментариям, параметры сортировки базы данных не чувствительны к регистру, поэтому SQL не может различить точные совпадения по регистру без добавления операторов COLLATE
.Это то, что мы не можем сделать с LINQ.