Производительность LINQ в случае Skip, Take & count

У меня есть следующий код, который отлично работает, и я получаю необходимые данные, как и ожидалось. Эти данные передаются во внешний интерфейс для отображения в виде сетки.

public object Test(SearchGridParam obj)
{
    List<GridModel> res = 
        (from data in _entity.TableNoTracking
        where (string.IsNullOrWhiteSpace(obj.Criticality) || data.CRITICALITY.Equals(obj.Criticality))
        && (string.IsNullOrWhiteSpace(obj.Search)
                || (!string.IsNullOrWhiteSpace(data.DESCRIPTION) && data.DESCRIPTION.ToUpper().Contains(obj.Search.ToUpper()))
                || (!string.IsNullOrWhiteSpace(data.NUMBER) && data.NUMBER.ToUpper().Contains(obj.Search.ToUpper()))
                )
        select new GridModel
        {
            ID = data.ID,
            NUMBER = data.NUMBER,
            CRITICALITY = data.CRITICALITY,
            WO_STATUS = data.WO_STATUS,
            ASSET_NUMBER = data.ASSET_NUMBER,
            DESCRIPTION = data.DESCRIPTION,
            HOURS = data.HOURS,
            START_DATE = data.START_DATE
        })
    .OrderByDescending(ord => ord.START_DATE)
    .ToList(); //Gives 300+ records depending on the filters

    int count = res.Count();

    return new Result
    {
        Result = res.Skip(obj.Skip).Take(obj.Take), // fetching only 10
        Count = count
    };
}

Контекст

Согласно требованию:

  • Мне нужен счет после применения условия «где». (Я достиг этого, используя: int count = res.Count();)
  • В конечном наборе результатов мне нужно только 10 записей за раз. (Достигается с помощью: res.Skip(obj.Skip).Take(obj.Take))
  • Я получаю данные из базы данных (dbContext).

Вопрос

Как видите, мне нужно всего 10 записей, используя skip & take. Но просто для подсчета мне приходится использовать ToList() (который загружает все данные в память). Есть ли другой оптимизированный способ сделать это? ИЛИ есть ли альтернативный подход к пропуску, взятию и подсчету?

Что я пробовал

  • Я пробовал следующее, чтобы улучшить производительность, но это занимает столько же времени (а иногда и больше, чем .ToList())
// *****code above is same*****
.OrderByDescending(ord => ord.START_DATE)
.AsQueryable();

int count = res.Count();

return new Result
{
    Result = res.Skip(obj.Skip).Take(obj.Take).ToList(),
    Count = count
};

Изменить 1

Я получаю данные из Oracle DB

«Мне нужно использовать ToList()» — нет, не нужно, просто используйте Count().

Guru Stron 14.04.2023 10:42

«Но просто для подсчета мне нужно использовать ToList()» — минимальный воспроизводимый пример было бы неплохо, и код, показывающий, как вы это измеряете (есть некоторые предостережения, касающиеся измерения времени). Но 300 сотен записей - это не так уж и много, поэтому их извлечение в память может быть быстрее, чем выполнение 2 запросов (в зависимости от фактической настройки). Также удалите Select и OrderBy..., они не нужны для Count, применяйте их только для финального запроса.

Guru Stron 14.04.2023 10:45

это то, что вы ищете? .OrderByDescending(ord => ord.START_DATE) .Skip(obj.Skip) .Take(obj.Take) .ToList();

jeb 14.04.2023 10:52
Contains выполняет сканирование таблицы при выполнении. Поэтому, если ваша таблица большая, запрос будет медленным. Для быстрого поиска нужно использовать FREETEXT
Svyatoslav Danyliv 14.04.2023 11:34

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

Oliver 14.04.2023 12:51

@GuruStron В моем исходном коде -> Если я удалю .OrderByDescending(...).ToList() -> И изменю результат на Result = res.OrderByDescending(...).Skip(obj.Skip).Take(obj.Take).ToL‌​ist(). В моем случае время выполнения почти удваивается, потому что и Count(), и ToList() занимают одинаковое время для обработки.

Sampath 14.04.2023 13:46

@Sampath, как ты измеряешь время? Где находится приложение по сравнению с базой данных?

Guru Stron 14.04.2023 13:48

Рассмотрите возможность использования разбиения на страницы набора ключей (разбиение на страницы по ключу) вместо разбиения на страницы набора строк (разбиение на страницы по номеру строки), см. stackoverflow.com/questions/70519518/…

Charlieface 14.04.2023 14:07

@GuruStron, как вы измеряете время? : Я использовал Stopwatch(). Впрочем, даже этого не требуется, поскольку задержка хорошо видна при отладке. Где находится приложение по сравнению с базой данных? : Я не уверен в точном местоположении, но его нет в моей локальной системе. У меня нет прямого доступа к БД.

Sampath 14.04.2023 14:22

Сколько раз вы запускали измерения перед перезапуском приложения? «Я не уверен в точном местоположении» - это может быть решающим моментом, который может заставить ваш код работать быстро или медленно.

Guru Stron 14.04.2023 14:30

Используйте такую ​​библиотеку, как PagedList. Дайте ему IQueryable, и он автоматически выполнит для него запрос Skip/Take и Count(), оба из которых запускаются в базе данных.

Gert Arnold 14.04.2023 14:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
130
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Результат использования Linq различается в зависимости от того, используете ли вы подключение к данным, такое как EntityFramework или подобное, или нет. В вашем случае может показаться, что вы используете EntityFramework, потому что вы запрашиваете из _entity.TableNoTracking, но как только вы используете терминальную операцию, такую ​​как ToList(), остальная часть выражения Linq использует Linq to Objects который работает в памяти.

Итак, когда вы выполняете свой запрос (все это до ToList), LINQ создаст запрос к базе данных. После ToList это делается в памяти, поэтому .Count выполняется в памяти.

Это важно, особенно с Skip, Take and count. На самом деле, если вы используете Skip and Take для запроса 10 записей с использованием базы данных, EntityFramewokr создаст оператор SQL, который сделает это — именно база данных выбирает 10 записей, а LINQ никогда не повторяет весь набор записей. Когда вы начинаете считать, это делается в памяти, и вы всегда получаете 10 или меньше.

Я бы предположил, что правильный способ сделать это (если вам нужен фактический размер набора записей) - это сделать следующее:

var corequery = (from data in _entity.TableNoTracking where ...  select ... ).AsQueryable();

var records = corequery.OrderBy().Skip().Take().ToList();

var countofrecords = corequery.Count();

var countofrecordsreturned = records.Count();

Учитывая вышеизложенное, Skip и Take составляют часть запроса к базе данных из-за появления перед терминалом. Второй запрос Count также отправляется на сервер, но он создает запрос вида «SELECT Count(*)» и возвращает только целое число. Затем у вас есть возможность подсчитать количество записей в наборе записей и отдельно подсчитать, сколько записей было возвращено. Это, очевидно, позволяет вам правильно рассчитать, сколько страниц данных у вас есть.

Если вас действительно беспокоит время приема и передачи второго запроса подсчета, я бы посоветовал вам отправлять запрос подсчета асинхронно. например

    var tcount = query.CountAsync();
    var data = await query.ToListAsync();
    var count await tcount;

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

Спасибо за ваш вклад. У меня есть последующее сомнение/наблюдение. В моем исходном запросе LINQ ToList() занимает x секунд. Когда я реализовал код, как вы предложили; запись строки занимает x секунд, а countofrecords снова занимает x секунд. Эффективно, почти удвоив время выполнения. Это симптом совсем другой проблемы? (Ps: я не использовал здесь секундомер, но во время отладки пауза понятна. Кроме того, при наблюдении с конца пользовательского интерфейса задержка очень заметна)

Sampath 16.04.2023 07:37

Все зависит от пула, блокировок базы данных, кеша запросов и способности базы данных выполнять эти два запроса параллельно. Обычно я использую либо Microsoft SQL, либо SAP Hana DB, SQL, как правило, дает вам подсчет, казалось бы, бесплатно — HANA может приостанавливаться, пока выполняет оператор Prepare, но в следующий раз, когда вам нужно будет сделать это, когда база данных загружена, это может быть намного сложнее. быстрее. Важно то, что CountAsync должен вернуться немедленно, и await tcount тоже должен быть немедленным - если это не так, то, возможно, ваша конфигурация пула базы данных не позволяет вам работать параллельно.

Mark Rabjohn 17.04.2023 17:29

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

Magnus 18.04.2023 09:05

Другие вопросы по теме