前回は、EF Core でレコードを追加する方法を見ました。
今回は、追加したデータを問い合わせる方法を整理します。
EF Core の問い合わせは LINQ で書けますが、最終的には SQL に変換されてデータベースで実行されます。
全件取得
もっとも単純な問い合わせです。
List<Car> cars = await context.Cars
.AsNoTracking()
.ToListAsync();
読み取り専用なら AsNoTracking() を付けると、変更追跡のコストを減らせます。
並び順を明示するなら OrderBy() を使います。
List<Car> cars = await context.Cars
.AsNoTracking()
.OrderBy(car => car.Id)
.ToListAsync();
データベースは、明示しない限り順序を保証しません。
画面表示やページングでは、必ず並び順を指定しましょう。
条件で絞り込む
string make = "Honda";
List<Car> cars = await context.Cars
.AsNoTracking()
.Where(car => car.Make == make)
.ToListAsync();
変数 make は SQL パラメーターとして扱われます。
複数条件も書けます。
List<Car> cars = await context.Cars
.AsNoTracking()
.Where(car => car.Make == "Honda" && car.Color == "Blue")
.ToListAsync();
文字列検索です。
List<Car> cars = await context.Cars
.AsNoTracking()
.Where(car => car.PetName.Contains("a"))
.ToListAsync();
文字列比較は、データベースの照合順序やプロバイダーによって挙動が変わることがあります。
並べ替える
List<Car> cars = await context.Cars
.AsNoTracking()
.OrderBy(car => car.Make)
.ThenBy(car => car.PetName)
.ToListAsync();
降順にする場合です。
List<Car> cars = await context.Cars
.AsNoTracking()
.OrderByDescending(car => car.Id)
.ToListAsync();
OrderBy() の後に別の OrderBy() を書くと、前の並び替えが上書きされます。
二次ソートには ThenBy() を使いましょう。
ページング
検索結果をページ単位で取得します。
int page = 2;
int pageSize = 10;
List<Car> cars = await context.Cars
.AsNoTracking()
.OrderBy(car => car.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
ページングでは、安定した並び順が必要です。
OrderBy() なしで Skip() / Take() を使うと、結果が安定しません。
総件数も取得できます。
int totalCount = await context.Cars.CountAsync();
画面では、totalCount と pageSize から総ページ数を計算できます。
1 件だけ取得する
主キーで探すなら FindAsync() が使えます。
Car? car = await context.Cars.FindAsync(1);
条件で 1 件取得するなら FirstOrDefaultAsync() がよく使われます。
Car? car = await context.Cars
.AsNoTracking()
.FirstOrDefaultAsync(car => car.PetName == "Vega");
必ず 1 件だけであることを確認したい場合は SingleOrDefaultAsync() を使います。
Car? car = await context.Cars
.AsNoTracking()
.SingleOrDefaultAsync(car => car.Id == 1);
SingleOrDefaultAsync() は、該当行が複数あると例外になります。
データの一意性を確認したい場合に向いています。
集計する
件数を取得します。
int count = await context.Cars.CountAsync();
条件付きの件数です。
int count = await context.Cars
.CountAsync(car => car.Make == "Honda");
最大値や最小値です。
int maxId = await context.Cars.MaxAsync(car => car.Id);
int minId = await context.Cars.MinAsync(car => car.Id);
グループ化もできます。
var counts = await context.Cars
.AsNoTracking()
.GroupBy(car => car.Make)
.Select(group => new
{
Make = group.Key,
Count = group.Count()
})
.ToListAsync();
集計処理は、SQL に変換されてデータベース側で実行されることが多いです。
存在確認と全件条件確認
存在確認には AnyAsync() を使います。
bool hasHonda = await context.Cars
.AnyAsync(car => car.Make == "Honda");
すべての行が条件を満たすか確認するには AllAsync() を使います。
bool allHaveNames = await context.Cars
.AllAsync(car => car.PetName != "");
存在確認だけなら、CountAsync() > 0 より AnyAsync() の方が意図が明確です。
必要な形に投影する
エンティティ全体が不要なら、必要な列だけ取得します。
var items = await context.Cars
.AsNoTracking()
.Select(car => new
{
car.Id,
Label = car.Color + " " + car.Make,
car.PetName
})
.ToListAsync();
画面や API で使うなら DTO を作ると読みやすくなります。
public sealed class CarListItem
{
public int Id { get; init; }
public string Label { get; init; } = "";
public string PetName { get; init; } = "";
}
List<CarListItem> items = await context.Cars
.AsNoTracking()
.Select(car => new CarListItem
{
Id = car.Id,
Label = car.Color + " " + car.Make,
PetName = car.PetName
})
.ToListAsync();
投影を使うと、不要な列やナビゲーションプロパティを読み込まずに済みます。
ストアドプロシージャから取得する
ストアドプロシージャや生 SQL からエンティティを取得することもできます。
List<Car> cars = await context.Cars
.FromSqlRaw("EXEC GetCarsByMake @p0", "Honda")
.ToListAsync();
ただし、結果の列がエンティティの形と一致している必要があります。
パラメーターを明示する場合です。
using Microsoft.Data.SqlClient;
var parameter = new SqlParameter("@make", "Honda");
List<Car> cars = await context.Cars
.FromSqlRaw("EXEC GetCarsByMake @make", parameter)
.ToListAsync();
生 SQL を使う場合でも、値の埋め込みは避け、パラメーターを使いましょう。
クエリを書くときの注意
EF Core の LINQ は便利ですが、すべての C# コードが SQL に変換できるわけではありません。
避けたい例です。
static bool IsSpecial(string value)
{
return value.StartsWith("A") && value.Length > 3;
}
List<Car> cars = await context.Cars
.Where(car => IsSpecial(car.PetName))
.ToListAsync();
独自メソッドは SQL に変換できない場合があります。
まずデータベース側で絞り込み、必要ならメモリ上で追加処理します。
List<Car> candidates = await context.Cars
.AsNoTracking()
.Where(car => car.PetName.StartsWith("A"))
.ToListAsync();
List<Car> result = candidates
.Where(car => IsSpecial(car.PetName))
.ToList();
まとめ
EF Core の問い合わせは LINQ で書けます。
全件取得、絞り込み、並べ替え、ページング、単一取得、集計、投影などを組み合わせることで、多くの読み取り処理を表現できます。
重要なのは、LINQ が最終的に SQL へ変換されることを意識することです。
次回は、関連データをどのタイミングで読み込むかを扱います。