web-dev-qa-db-ja.com

エンティティフレームワーク4.1でデータのページを照会して合計数を取得するより良い方法は?

現在、ページングで使用されるクエリを実行する必要がある場合、次のようにします:

//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を使用しながら、これをより効率的に改善することが可能かどうか疑問に思いましたか?ストアドプロシージャを使用しないDBへの1回のトリップで、データ取得とカウントを組み合わせる方法を考えることはできませんでした。

54
C.J.

次のクエリは、データベースへの1回の旅行でカウントとページの結果を取得しますが、LINQPadでSQLを確認すると、あまりきれいではないことがわかります。より複雑なクエリではどうなるか想像できます。

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回の旅行、または1回の旅行でGroupByを使用)を使用し、大きな違いに気付かないでしょう。複雑なものについては、ストアドプロシージャが最適なソリューションだと思います。

75
Jeff Ogata

ジェフ・オガタの答えは少し最適化できます。

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を煩わせないことを除いて、ほとんど同じことを行います。クエリに少なくとも1つの結果が含まれていて、例外をスローしたくない場合は、次のように(よりクリーンではありませんが)totalCountを取得できます。

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

EF Core> = 1.1.x && <3.0.0:を使用している人への重要な注意

当時私はこれに対する解決策を探していましたが、このページはグーグル用語「EF Core Paging Total Count」のランク1です。

SQLプロファイラーをチェックした結果、EFが返されるすべての行に対してSELECT COUNT(*)を生成することがわかりました。このページで提供されているすべてのソリューションに飽きています。

これは、EF Core 2.1.4とSQL Server 2014を使用してテストされました。最終的に、2つの別個のクエリとして実行する必要がありました。少なくとも私にとっては、これは世界の終わりではありません。

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/13739https://github.com/aspnet/EntityFrameworkCore/issues/11186

5
SimonGates

最初のページに対して2つのクエリを作成することをお勧めします。1つは合計カウント用、もう1つは最初のページまたは結果用です。

最初のページを超えて移動するときに使用する合計カウントをキャッシュします。

4
Bryan