У меня есть следующий код, который отлично работает, и я получаю необходимые данные, как и ожидалось. Эти данные передаются во внешний интерфейс для отображения в виде сетки.
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();
)res.Skip(obj.Skip).Take(obj.Take)
)Как видите, мне нужно всего 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
};
Я получаю данные из Oracle DB
«Но просто для подсчета мне нужно использовать ToList()» — минимальный воспроизводимый пример было бы неплохо, и код, показывающий, как вы это измеряете (есть некоторые предостережения, касающиеся измерения времени). Но 300 сотен записей - это не так уж и много, поэтому их извлечение в память может быть быстрее, чем выполнение 2 запросов (в зависимости от фактической настройки). Также удалите Select
и OrderBy...
, они не нужны для Count
, применяйте их только для финального запроса.
это то, что вы ищете? .OrderByDescending(ord => ord.START_DATE) .Skip(obj.Skip) .Take(obj.Take) .ToList();
Contains
выполняет сканирование таблицы при выполнении. Поэтому, если ваша таблица большая, запрос будет медленным. Для быстрого поиска нужно использовать FREETEXTВ то, что вы пробовали, вы добавляете .AsQueryable()
. Это выглядит подозрительно для меня и не должно быть необходимо. Если вы не можете работать без этого, пожалуйста, опубликуйте всю функцию еще раз, чтобы мы могли найти ошибку. Все остальное выглядит нормально.
@GuruStron В моем исходном коде -> Если я удалю .OrderByDescending(...).ToList()
-> И изменю результат на Result = res.OrderByDescending(...).Skip(obj.Skip).Take(obj.Take).ToList()
. В моем случае время выполнения почти удваивается, потому что и Count()
, и ToList()
занимают одинаковое время для обработки.
@Sampath, как ты измеряешь время? Где находится приложение по сравнению с базой данных?
Рассмотрите возможность использования разбиения на страницы набора ключей (разбиение на страницы по ключу) вместо разбиения на страницы набора строк (разбиение на страницы по номеру строки), см. stackoverflow.com/questions/70519518/…
@GuruStron, как вы измеряете время? : Я использовал Stopwatch()
. Впрочем, даже этого не требуется, поскольку задержка хорошо видна при отладке. Где находится приложение по сравнению с базой данных? : Я не уверен в точном местоположении, но его нет в моей локальной системе. У меня нет прямого доступа к БД.
Сколько раз вы запускали измерения перед перезапуском приложения? «Я не уверен в точном местоположении» - это может быть решающим моментом, который может заставить ваш код работать быстро или медленно.
Используйте такую библиотеку, как PagedList. Дайте ему IQueryable
, и он автоматически выполнит для него запрос Skip/Take и Count()
, оба из которых запускаются в базе данных.
Результат использования 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: я не использовал здесь секундомер, но во время отладки пауза понятна. Кроме того, при наблюдении с конца пользовательского интерфейса задержка очень заметна)
Все зависит от пула, блокировок базы данных, кеша запросов и способности базы данных выполнять эти два запроса параллельно. Обычно я использую либо Microsoft SQL, либо SAP Hana DB, SQL, как правило, дает вам подсчет, казалось бы, бесплатно — HANA может приостанавливаться, пока выполняет оператор Prepare, но в следующий раз, когда вам нужно будет сделать это, когда база данных загружена, это может быть намного сложнее. быстрее. Важно то, что CountAsync должен вернуться немедленно, и await tcount тоже должен быть немедленным - если это не так, то, возможно, ваша конфигурация пула базы данных не позволяет вам работать параллельно.
Если запрос требует больших затрат для выполнения, скажем, базовые таблицы содержат миллионы строк, а результирующий набор мал, выполнение его дважды не является хорошим вариантом, даже если выполняется параллельно. С другой стороны, если запрос быстрый, но набор результатов большой, верно обратное.
«Мне нужно использовать ToList()» — нет, не нужно, просто используйте
Count()
.