web-dev-qa-db-fra.com

Meilleure façon d'interroger une page de données et d'obtenir le nombre total dans le framework d'entité 4.1

Actuellement, quand j'ai besoin d'exécuter une requête qui sera utilisée avec pagination, je le fais comme ceci:

//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();

Cela fonctionne, mais je me suis demandé s'il était possible d'améliorer cela pour être plus efficace tout en utilisant linq. Je ne pouvais pas penser à un moyen de combiner le nombre avec la récupération de données en un seul voyage à la base de données sans utiliser un proc stocké.

50
C.J.

La requête suivante obtiendra le nombre et les résultats de page en un seul voyage vers la base de données, mais si vous vérifiez le code SQL dans LINQPad, vous verrez que ce n'est pas très joli. Je ne peux qu'imaginer à quoi ressemblerait une requête plus complexe.

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);

Pour une requête simple comme celle-ci, vous pouvez probablement utiliser l'une ou l'autre méthode (2 déplacements dans la base de données ou utiliser GroupBy pour le faire en 1 voyage) et ne pas remarquer beaucoup de différence. Pour quelque chose de complexe, je pense qu'une procédure stockée serait la meilleure solution.

73
Jeff Ogata

La réponse de Jeff Ogata peut être optimisée un peu.

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();

Cela fait à peu près la même chose, sauf que cela ne gênera pas la base de données avec un GROUP BY inutile. Lorsque vous n'êtes pas certain que votre requête contiendra au moins un résultat et que vous ne voulez pas qu'elle jette une exception, vous pouvez obtenir totalCount de la manière suivante (bien que moins nette):

var totalCount = results.FirstOrDefault()?.TotalCount ?? 0;
6
Rudey

Je suggère de faire deux requêtes pour la première page, une pour le nombre total et une pour la première page ou les résultats. 

Mettez en cache le nombre total à utiliser lorsque vous vous déplacez au-delà de la première page.

3
Bryan

Remarque importante pour les personnes utilisant EF Core> = 1.1.x:

À l'époque, je cherchais une solution à ce problème et cette page est/était le rang 1 pour le terme Google "Nombre total de pagettes EF".

Après avoir vérifié le profileur SQL J’ai constaté que EF génère un SELECT COUNT(*) pour chaque ligne renvoyée J'ai fatigué chaque solution fournie sur cette page.

Cela a été testé avec EF Core 2.1.4 et SQL Server 2014. À la fin, je devais les exécuter en tant que deux requêtes distinctes comme ceci. Ce qui, pour moi du moins, n'est pas la fin du monde.

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             
};

Il semble que l'équipe EF Core en soit consciente:

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

0
SimonGates