目次
概要
Entity Framework Core (EF Core) を使用して、データベースからデータを取得する基本的なパターンを解説します。 全件取得だけでなく、LINQの Where メソッドを使った条件による絞り込みや、FirstOrDefaultAsync を使った単一レコードの検索など、実務で頻出するクエリの実装方法を紹介します。
仕様(入出力)
- 入力: 検索条件(ID、名前、数値の範囲など)。
- 処理:
ToListAsync: 条件に合うすべての行をリストとして取得。FirstOrDefaultAsync: 条件に合う最初の1行を取得(なければnull)。Where: 検索条件(フィルタ)を定義。
- 出力: データベースから抽出されたエンティティオブジェクト、またはそのリスト。
基本の使い方
System.Linq 名前空間の拡張メソッドを使用してクエリを構築し、最後に非同期メソッドで実行します。
// 特定の条件(例: 価格が1000以上)に一致するデータをリストで取得
var expensiveItems = await context.Items
.Where(i => i.Price >= 1000)
.ToListAsync();
// IDが一致するデータを1件だけ取得
var item = await context.Items
.FirstOrDefaultAsync(i => i.Id == 5);
コード全文
以下のコードは、ECサイトの「顧客(Customer)」と「注文(Order)」を管理するシステムを想定し、条件に応じたデータ抽出を行う完全なコンソールアプリケーションです。 動作確認のため、実行時に初期データを自動投入しています。
※ 実行には Microsoft.EntityFrameworkCore.InMemory パッケージが必要です。 dotnet add package Microsoft.EntityFrameworkCore.InMemory
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace QueryDataSample
{
// 1. エンティティクラスの定義
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
// 1人の顧客は複数の注文を持つ
public List<Order> Orders { get; set; } = new List<Order>();
}
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public int CustomerId { get; set; }
public Customer? Customer { get; set; }
}
// 2. DbContextの定義
public class ShopContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase("ShopDb");
}
}
class Program
{
static async Task Main(string[] args)
{
// 初期データの投入
await SeedDataAsync();
using (var context = new ShopContext())
{
Console.WriteLine("=== 1. 全件取得 ===");
// ToListAsyncですべてのデータをメモリ上に展開します
var allCustomers = await context.Customers.ToListAsync();
foreach (var c in allCustomers)
{
Console.WriteLine($"ID: {c.Id}, Name: {c.Name}");
}
Console.WriteLine("\n=== 2. 条件に一致する1件を取得 (FirstOrDefault) ===");
// 名前が "Alice" の顧客を検索
var targetCustomer = await context.Customers
.FirstOrDefaultAsync(c => c.Name == "Alice");
if (targetCustomer != null)
{
Console.WriteLine($"Found: {targetCustomer.Name} ({targetCustomer.Email})");
}
else
{
Console.WriteLine("Not Found.");
}
Console.WriteLine("\n=== 3. 条件による絞り込み検索 (Where) ===");
// 金額が 5000円 を超える注文を検索
var highValueOrders = await context.Orders
.Where(o => o.TotalAmount > 5000m)
.ToListAsync();
foreach (var order in highValueOrders)
{
Console.WriteLine($"Order ID: {order.Id}, Amount: {order.TotalAmount:C}, Date: {order.OrderDate:d}");
}
}
}
// データ投入用ヘルパーメソッド
static async Task SeedDataAsync()
{
using (var context = new ShopContext())
{
if (await context.Customers.AnyAsync()) return;
var c1 = new Customer { Name = "Alice", Email = "alice@test.com" };
var c2 = new Customer { Name = "Bob", Email = "bob@test.com" };
context.Customers.AddRange(c1, c2);
await context.SaveChangesAsync();
context.Orders.AddRange(
new Order { CustomerId = c1.Id, TotalAmount = 1200m, OrderDate = DateTime.Now.AddDays(-10) },
new Order { CustomerId = c1.Id, TotalAmount = 8500m, OrderDate = DateTime.Now.AddDays(-5) }, // 5000超え
new Order { CustomerId = c2.Id, TotalAmount = 600m, OrderDate = DateTime.Now.AddDays(-1) }
);
await context.SaveChangesAsync();
}
}
}
}
実行結果例
=== 1. 全件取得 ===
ID: 1, Name: Alice
ID: 2, Name: Bob
=== 2. 条件に一致する1件を取得 (FirstOrDefault) ===
Found: Alice (alice@test.com)
=== 3. 条件による絞り込み検索 (Where) ===
Order ID: 2, Amount: \8,500, Date: 2026/01/07
カスタムポイント
- 主キー検索の最適化 ID(主キー)で検索する場合は、
FirstOrDefaultよりもFind(またはFindAsync)メソッドを使用すると、コンテキスト内のキャッシュを優先して参照するため高速になる場合があります。C#var user = await context.Users.FindAsync(10); - 読み取り専用クエリの高速化 データを表示するだけで変更(更新)する予定がない場合は、
AsNoTracking()を追加すると、変更追跡のオーバーヘッドがなくなりパフォーマンスが向上します。C#var logs = await context.Logs.AsNoTracking().ToListAsync();
注意点
- クライアント評価 vs サーバー評価
Whereの中にC#独自のメソッド(例:String.Formatや自作メソッド)を含めると、SQLに変換できず、全データをメモリに取得してからフィルタリングする場合があり、著しい性能低下を招きます。検索条件は可能な限りプリミティブな型や標準的な演算子で行ってください。 - NULLチェック
FirstOrDefaultAsyncはデータが見つからない場合にnullを返します。戻り値にアクセスする前に必ずnullチェックを行ってください(SingleAsyncなどは見つからないと例外をスローします)。 - 実行タイミング
Whereを書いただけではSQLは発行されません。ToListAsync、FirstOrDefaultAsync、CountAsyncなどの終端メソッドを呼び出した時点で初めてデータベースへのクエリが実行されます(遅延実行)。
バリエーション(任意)
複数の条件を組み合わせる
AND条件(&&)やOR条件(||)を使って複雑なクエリを構築できます。
// 「Aliceさん」の注文 かつ 「2025年以降」のデータ
var specificOrders = await context.Orders
.Where(o => o.Customer.Name == "Alice" && o.OrderDate.Year >= 2025)
.OrderByDescending(o => o.OrderDate) // 日付の新しい順
.ToListAsync();
まとめ
クエリはデータベース上で実行されるよう、SQL変換可能な式で記述することが重要です。
複数行の取得には Where + ToListAsync を使用します。
単一行の取得には FirstOrDefaultAsync を使用し、nullチェックを忘れないようにします。
