Разбиение на страницы

Разбиение на страницы означает получение результатов на страницах, а не всех одновременно; Обычно это делается для больших наборов результатов, где отображается пользовательский интерфейс, позволяющий пользователю перейти к следующей или предыдущей странице результатов.

Предупреждение

Независимо от используемого метода разбиения на страницы всегда убедитесь, что заказ полностью уникален. Например, если результаты упорядочены только по дате, но могут быть несколько результатов с одной датой, результаты могут быть пропущены при разбиении на страницы, так как они упорядочены по-разному в двух запросах на страницы. Упорядочение по дате и идентификатору (или любому другому уникальному свойству или сочетанию свойств) делает заказ полностью уникальным и позволяет избежать этой проблемы. Обратите внимание, что реляционные базы данных по умолчанию не применяют порядок даже для первичного ключа.

Смещение разбиения на страницы

Распространенным способом реализации разбиения на страницы с базами данных является использование Skip и Take (OFFSET и LIMIT в SQL). Учитывая размер страницы 10 результатов, третью страницу можно получить с помощью EF Core следующим образом:

var position = 20;
var nextPage = context.Posts
    .OrderBy(b => b.PostId)
    .Skip(position)
    .Take(10)
    .ToList();

К сожалению, хотя этот метод очень интуитивно понятен, он также имеет некоторые серьезные недостатки:

  1. База данных по-прежнему должна обрабатывать первые 20 записей, даже если они не возвращаются в приложение; Это создает, возможно, значительную вычислительную нагрузку, которая увеличивается с числом пропущенных строк.
  2. Если какие-либо обновления происходят одновременно, разбивка на страницы может в конечном итоге пропускать определенные записи или показывать их дважды. Например, если запись удаляется по мере перемещения пользователя с страницы 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, что отслеживается no 26822.

Индексы

Как и в случае с любым другим запросом, правильное индексирование имеет жизненно важное значение для хорошей производительности: убедитесь, что у вас есть индексы, соответствующие упорядочению на страницы. При упорядочении по нескольким столбцам можно определить индекс по нескольким столбцам; это называется составным индексом.

Дополнительные сведения см. на странице документации по индексам.

Дополнительные ресурсы