Разбиение на страницы
Разбивка на страницы ссылается на получение результатов в страницах, а не все одновременно; Обычно это делается для больших наборов результатов, где отображается пользовательский интерфейс, позволяющий пользователю перейти к следующей или предыдущей странице результатов.
Предупреждение
Независимо от используемого метода разбиения на страницы всегда убедитесь, что порядок полностью уникален. Например, если результаты упорядочены только по дате, но могут быть несколько результатов с одной и той же датой, результаты могут быть пропущены при разбиении на страницы по мере их упорядочения по двум запросам на страницы. Упорядочивание по дате и идентификатору (или любому другому уникальному свойству или сочетанию свойств) делает порядок полностью уникальным и помогает избежать этой проблемы. Обратите внимание, что по умолчанию в реляционных базах данных не применяется упорядочение, даже по первичному ключу.
Примечание.
Azure Cosmos DB имеет собственный механизм для разбиения на страницы, см. на странице выделенной документации.
Смещение на страницы
Распространенный способ реализации разбиения на страницы с базами данных — использовать Skip
Take
операторы LINQ (OFFSET
и LIMIT
в SQL). Учитывая размер страницы 10 результатов, третью страницу можно получить с помощью EF Core следующим образом:
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
К сожалению, в то время как этот метод очень интуитивно понятен, он также имеет некоторые серьезные недостатки:
- База данных по-прежнему должна обрабатывать первые 20 записей, даже если они не возвращаются в приложение; это создает, возможно, значительную нагрузку вычислений, которая увеличивается с числом пропущенных строк.
- Если какие-либо обновления происходят одновременно, ваша разбивка на страницы может в конечном итоге пропустить определенные записи или показать их дважды. Например, если запись удаляется по мере перемещения пользователя с страницы 2 на 3, весь набор результатов "сдвигается вверх", а одна запись будет пропущена.
Разбиение на страницы набора ключей
Рекомендуемая альтернатива смещению на страницы на основе смещения , иногда называемая разбивкой на страницы набора ключей или разбиением на страницы на основе поиска, заключается в том, чтобы просто использовать WHERE
предложение для пропуска строк вместо смещения. Это означает, что помните соответствующие значения из последней записи, полученной (вместо смещения), и попросите следующие строки после этой строки. Например, при условии, что последняя запись на последней странице, которую мы извлекли, имела значение идентификатора 55, мы просто сделали следующее:
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
Предполагая, что индекс определен PostId
, этот запрос очень эффективен, а также не учитывается при параллельных изменениях, происходящих в более низких значениях идентификаторов.
Разбиение на страницы набора ключей подходит для интерфейсов разбиения на страницы, где пользователь перемещается вперед и назад, но не поддерживает случайный доступ, где пользователь может перейти на любую определенную страницу. Для разбиения на страницы случайного доступа требуется использование смещения на страницы, как описано выше; из-за недостатков смещения разбиения на страницы тщательно рассмотрите, действительно ли для вашего варианта использования требуется случайное разбиение на страницы, или если навигация по следующей/предыдущей странице достаточно. Если требуется случайное разбиение на страницы, надежная реализация может использовать разбиение набора ключей при переходе на следующую/предыдущую страницу и навигацию смещения при переходе на любую другую страницу.
Несколько ключей разбиения на страницы
При использовании разбиения на страницы набора ключей часто необходимо упорядочивать по нескольким свойствам. Например, следующие разбивки запросов на страницы по дате и идентификатору:
var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.Date)
.ThenBy(b => b.PostId)
.Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
.Take(10)
.ToList();
Это гарантирует, что следующая страница выбирает именно место окончания предыдущей страницы. При добавлении дополнительных ключей упорядочивания можно добавить дополнительные предложения.
Примечание.
Большинство баз данных SQL поддерживают более простую и эффективную версию приведенного выше, используя значения строк: WHERE (Date, Id) > (@lastDate, @lastId)
EF Core в настоящее время не поддерживает выражение этого в запросах LINQ, это отслеживается #26822.
Индексы
Как и в случае с любым другим запросом, правильное индексирование жизненно важно для хорошей производительности: обязательно укажите индексы, соответствующие заказу на страницы. При упорядочении по нескольким столбцам можно определить индекс по нескольким столбцам; это называется составным индексом.
Дополнительные сведения см. на странице документации по индексам.
Дополнительные ресурсы
- Дополнительные сведения о недостатках смещения на основе разбиения на страницы и о разбиении на страницы набора ключей см . в этой записи.
- Сеанс стенда сообщества данных .NET, в котором мы обсудим разбиение на страницы и проверим все описанные выше понятия.
- Техническая подробное представление для сравнения смещения и разбиения на страницы набора ключей. Хотя содержимое связано с базой данных PostgreSQL, общие сведения также допустимы для других реляционных баз данных.
- Расширения на вершине EF Core, упрощающие разбиение на страницы набора ключей, см. в статье MR. EntityFrameworkCore.KeysetPagination и MR. AspNetCore.Pagination.