Разбивка на страницы и Структура сущностей

0

Вопрос

В своем мобильном приложении я пытаюсь извлечь данные из таблицы базы данных SQL Server. Я использую EF и стараюсь использовать разбиение на страницы для повышения производительности. Мне нужно извлечь данные из последнего элемента таблицы. поэтому, если в таблице 20 строк, мне нужны для страницы 0 идентификаторы 20, 19, 18, 17, 16, затем для страницы 1 идентификаторы 15, 14, 13, 12, 11 и так далее...

Проблема в следующем: что, если, пока пользователь "А" загружает данные из таблицы, пользователь "Б" добавляет строку? Если пользователь "А" получит страницу 0 (так что идентификаторы 20, 19, 18, 17, 16), и пользователь "B" в тот же момент добавит строку (так что идентификатор 21), с помощью классического запроса пользователь "A" для страницы 1 получит идентификаторы 16, 15, 14, 13, 12... так что в другой раз ID 16

Мой код очень прост:

int RecordsForPagination = 5; 
var list = _context.NameTable
                   .Where(my_condition)
                   .OrderByDescending(my_condition_for ordering)
                   .Skip (RecordsForPagination * Page)
                   .Take (RecordsForPagination)
                   .ToList();

Конечно Page это int, которые поступают с интерфейса.

Как я могу решить эту проблему?

Я нашел решение, но не знаю, является ли оно идеальным. Я мог бы использовать

.SkipWhile(x => x.ID >= LastID) 

вместо

.Skip (RecordsForPagination * Page)

и конечно же LastID всегда отправляется с интерфейса.

Как вы думаете, всегда ли с этим кодом хорошая производительность? Есть ли лучшее решение?

entity-framework linq sql-server
2021-11-22 23:06:34
1

Лучший ответ

1

Влияние на производительность будет в значительной степени зависеть от реализации индекса SQL и предложения order by. Но речь идет не столько о производительности, сколько о получении ожидаемых результатов.

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

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

Часто бывает достаточно объяснить пользователям, почему это происходит, во многих случаях они примут это

Если для вас было важно сохранить место в исходном результирующем наборе, то вам следует ограничить запрос Where предложение, но вам нужно будет получить либо идентификатор, либо метку времени в исходном запросе. В вашем случае вы пытаетесь использовать LastID, но для получения последнего идентификатора потребуется отдельный собственный запрос, потому что предложение orderby повлияет на него.

Вы действительно не можете использовать .SkipWhile(x => x.ID >= LastID) для этого, поскольку пропуск-это последовательный процесс, на который влияет порядок, и он отключается в первом экземпляре, который вычисляется выражением false, поэтому, если ваш заказ не основан на Id, ваш пропуск в то время как может привести к пропуску вообще никаких записей.

int RecordsForPagination = 5; 
int? MaxId = null;
...
var query = _context.NameTable.Where(my_condition);
// We need the Id to constraint the original search
if (!MaxId.HasValue)
    MaxId = query.Max(x => x.ID);

var list = query.Where(x => x.ID <= MaxId)
                .OrderByDescending(my_condition_for ordering)
                .Skip(RecordsForPagination * Page)
                .Take(RecordsForPagination);
                .ToList();

Как правило, проще фильтровать по моменту времени, так как это известно от клиента без возврата в базу данных, но в зависимости от реализации фильтрация по датам может быть менее эффективной.

2021-11-22 23:55:04

Моим намерением не было извлекать lastID из БД (потому что, как вы сказали, на это повлияла бы сама Бд). мое намерение состояло в следующем: - Интерфейс извлекает страницу 0, а результат-идентификаторы 20, 19, 18, 17, 16 - Интерфейс извлекает страницу 1 и передает бэкенду два параметра: Страница 1 и последний идентификатор предыдущей страницы (в данном случае последний = 16) -> таким образом, результатом будут идентификаторы 15, 14, 13, 12, 11... и так далее. мой английский не идеален... мы говорим одно и то же?
user1106897

@user1106897 Метод, на который ссылается Крис, называется разбиением набора ключей на страницы и, как правило, намного лучше, чем разбиение по строкам, о чем вы и говорите
Charlieface

Это также очень важно для производительности: разбиение на страницы по строкам крайне неэффективно, так как все предыдущие строки нужно каждый раз считывать. В то время как подкачка по ключу (или по времени в данном случае) очень эффективна, если есть поддерживающий индекс
Charlieface

Чарли Фейс большое вам спасибо за ссылку, очень признателен. Я постараюсь воспользоваться советами
user1106897

Спасибо @Charlieface замечание о производительности больше должно было звучать так: "забудьте о производительности, пропуск(по идентификатору) не решит проблему, если вы измените заказ" Отличная ссылка тоже!
Chris Schaller

На других языках

Эта страница на других языках

Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................

Популярное в этой категории

Популярные вопросы в этой категории