Лучший способ запросить страницу данных и получить общее количество в рамках структуры 4.1? - PullRequest
54 голосов
/ 14 октября 2011

В настоящее время, когда мне нужно выполнить запрос, который будет использоваться без разбивки на страницы, я делаю это примерно так:

//Setup query (Typically much more complex)
var q = ctx.People.Where(p=>p.Name.StartsWith("A"));

//Get total result count prior to sorting
int total = q.Count();       

//Apply sort to query
q = q.OrderBy(p => p.Name);  

q.Select(p => new PersonResult
{
   Name = p.Name
}.Skip(skipRows).Take(pageSize).ToArray();

Это работает, но я спрашиваю себя, можно ли улучшить это, чтобы сделать его более эффективным, продолжая использовать linq? Я не мог придумать, как объединить счет с поиском данных за одну поездку в БД без использования сохраненного процесса.

Ответы [ 4 ]

75 голосов
/ 14 октября 2011

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

var query = ctx.People.Where (p => p.Name.StartsWith("A"));

var page = query.OrderBy (p => p.Name)
                .Select (p => new PersonResult { Name = p.Name } )          
                .Skip(skipRows).Take(pageSize)
                .GroupBy (p => new { Total = query.Count() })
                .First();

int total = page.Key.Total;
var people = page.Select(p => p);

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

8 голосов
/ 26 июня 2018

Ответ Джеффа Огаты может быть немного оптимизирован.

var results = query.OrderBy(p => p.Name)
                   .Select(p => new
                   {
                       Person = new PersonResult { Name = p.Name },
                       TotalCount = query.Count()
                   })          
                   .Skip(skipRows).Take(pageSize)
                   .ToArray(); // query is executed once, here

var totalCount = results.First().TotalCount;
var people = results.Select(r => r.Person).ToArray();

Это делает почти то же самое, за исключением того, что не будет мешать базе данных с ненужным GROUP BY.Если вы не уверены, что ваш запрос будет содержать хотя бы один результат, и не хотите, чтобы он когда-либо выдавал исключение, вы можете получить totalCount следующим (хотя и менее чистым) способом:

var totalCount = results.FirstOrDefault()?.TotalCount ?? 0;
4 голосов
/ 10 августа 2015

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

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

3 голосов
/ 17 января 2019

Важное примечание для людей, использующих EF Core> = 1.1.x:

В то время, когда я искал решение этой проблемы, эта страница была / была рангом 1 по термину Google«Общее количество страниц подкачки EF».

После проверки профилировщика SQL я обнаружил, что EF генерирует SELECT COUNT(*) для каждой возвращаемой строки .Мне надоело каждое решение, представленное на этой странице.

Это было протестировано с использованием EF Core 2.1.4 и SQL Server 2014. В конце концов мне пришлось выполнить их как два отдельных запроса, например.Что, по крайней мере для меня, не конец света.

var query = _db.Foo.AsQueryable(); // Add Where Filters Here.


var resultsTask = query.OrderBy(p => p.ID).Skip(request.Offset).Take(request.Limit).ToArrayAsync();
var countTask = query.CountAsync();

await Task.WhenAll(resultsTask, countTask);

return new Result()
{
    TotalCount = await countTask,
    Data = await resultsTask,
    Limit = request.Limit,
    Offset = request.Offset             
};

Похоже, что команда EF Core знает об этом:

https://github.com/aspnet/EntityFrameworkCore/issues/13739 https://github.com/aspnet/EntityFrameworkCore/issues/11186

...