SQL Server のテーブルからレコードを抽出する際、あるフィールドに ORDER BY 句を適用して並べ替え、その順序で連番を振りたいという場合は ROW_NUMBER (Transact-SQL) を使うことができます。
上の画像がその例で、ProductName フィールドに ORDER BY 句を適用して昇順に並べ替えて、ROW_NUMBER を使ってその順で 1 から連番を振って、その連番を SeqNum という名前で取得しています。期待通り SeqNum に連番が取得されているのが分かるでしょうか?
それを同じことを Entity Framework ではどのようしたらできるかというのがこの記事の話です。
この記事を書いた時点で自分が調べた限りですが、ROW_NUMBER は Entity Framework ではサポートされてないようで(NuGet から Thinktecture.EntityFrameworkCore.SqlServer をインストールして使えるようにするという手はあるようですが)、Linq to Entities / Objects のクエリ式やメソッド式に ROW_NUMBER を含めることはできないようです。
Microsoft のドキュメント「生 SQL クエリ」に紹介されているように、FromSqlRaw 拡張メソッドを使用して上の画像の SELECT クエリをそのまま SQL Server に投げるという手も考えましたが、戻ってきた結果から SeqNum を取得する方法が見つかりません。(自分が見つけられないだけで手はあるのかもしれませんが)
ではどうするかですが、ググって調べていると Generating Sequence Numbers In LINQ Query という記事を見つけました。
IEnumerable<T> インターフェイスの Select 拡張メソッドのオーバーロードの一つに Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,Int32,TResult>) があって、それを使えば 0 番から始まる連番の index を取得することができるというものです。
その記事を参考に、上の画像の ROW_NUMBER を使った SELECT クエリと同じ結果を Linq to Entities / Objects を使って取得するコードを書いてみました。以下の通りです。
public List<ProductWithSeqNum> GetListWithSeqNum()
{
var data = _context.Products
.Select(a => new
{
ProductId = a.ProductId,
ProductName = a.ProductName,
UnitPrice = a.UnitPrice.Value
});
var list = data.AsEnumerable()
.OrderBy(a => a.ProductName)
.Select((a, index) => new ProductWithSeqNum
{
SeqNum = index + 1,
ProductId = a.ProductId,
ProductName = a.ProductName,
UnitPrice = a.UnitPrice
});
return list.ToList();
}
まず、Linq to Entities を使って Products テーブルから ProductId, ProductName, UnitPrice 抽出して IQueryable<匿名クラス> のオブジェクトを取得し、変数 data に格納しています。
次に、data.AsEnumerable() で IEnumerable<匿名クラス> に変換して、それを OrderBy メソッドで ProductName 順に並べ替え、上に述べた Select メソッドの index を使って 1 から始まる連番を SeqNum に格納しています。
ProductWithSeqNum クラスを Data Transfer Object (DTO) として使っていて、結果を List<ProductWithSeqNum> オブジェクトとして戻しています。ProductWithSeqNum クラスの定義は以下の通りです。
public class ProductWithSeqNum
{
public int SeqNum { get; set; }
public int ProductId { get; set; }
public string ProductName { get; set; }
public decimal UnitPrice { get; set; }
}
上の GetListWithSeqNum メソッドで取得した結果をコンソールに書き出すと以下の通りとなります。一番上の画像と同様な結果が取得できているのが分かるでしょうか。