コンパイル済みクエリとパフォーマンス
EF.CompileQuery、AsNoTracking、Selectによるプロジェクションを使って、クエリのパフォーマンスを高めます。
「コンパイル済みクエリとパフォーマンス」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
EF Coreのクエリコンパイル
EF CoreがLINQクエリを実行するたびに、式ツリーを解析し、SQLに変換して、検証する必要があります。このオーバーヘッドは、頻繁に実行される処理では積み重なります。EF Coreには、これを最小限に抑えるためのツールがいくつか用意されています。
読み取り専用クエリでのAsNoTracking
AsNoTracking()は変更追跡への登録をスキップします。読み取り専用クエリ(APIレスポンスやレポート)では、最も効果の大きい手軽な改善策です。通常は2~5倍高速になり、メモリ使用量も少なくなります。
// With tracking (default) — for entities you'll modify
var tracked = await _db.Products.FirstOrDefaultAsync(p => p.Id == 1);
// Without tracking — for read-only
var dto = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.Select(p => new ProductDto { Id = p.Id, Name = p.Name })
.ToListAsync();コンパイル済みクエリ
EF.CompileAsyncQueryはLINQクエリをデリゲートに事前コンパイルします。変換は起動時に一度だけ行われ、その後の呼び出しではコンパイル処理が完全にスキップされます。
// Define once, reuse many times
private static readonly Func<AppDbContext, int, Task<Product?>> GetProductById =
EF.CompileAsyncQuery(
(AppDbContext db, int id) =>
db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id));
// Usage
var product = await GetProductById(_db, 42);パフォーマンスのためのSelectによるプロジェクション
必要なフィールドだけを正確にプロジェクションしてください。完全なエンティティではなくDTOを選択すると、取得する列、ネットワーク転送量、GCの負荷を削減できます。
// Load entire entity (over-fetch)
var all = await _db.Products.ToListAsync();
// Projection: only fetch needed columns
var names = await _db.Products
.AsNoTracking()
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// SQL: SELECT Id, Name, Price FROM ProductsインデックスヒントとRaw Queryの最適化
EF CoreはHasIndex()で構成したデータベースインデックスを利用します。複合インデックス、フィルター付きインデックス、インクルード列はすべてクエリプランの性能に影響します。
// Configure compound index in OnModelCreating
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.CustomerId, o.Status })
.HasFilter("[Status] = 0") // filtered index
.IncludeProperties(o => o.PlacedAt);
// The query below will use this index efficiently
var pending = await _db.Orders
.AsNoTracking()
.Where(o => o.CustomerId == 5 && o.Status == OrderStatus.Pending)
.ToListAsync();SkipとTakeによるページング
大量の結果セットは必ずページングしてください。SkipとTakeはSQLのOFFSET/FETCHに変換されるため、数千行をメモリに読み込まずに済みます。
int page = 2, pageSize = 25;
var products = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// SQL: ... ORDER BY Name OFFSET 25 ROWS FETCH NEXT 25 ROWS ONLY効率的なカウント
件数を数えるだけなら、レコードを読み込む代わりにCountAsync()またはAnyAsync()を使用してください。存在するかどうかを確認するだけの場合、AnyAsync()はCOUNTよりもさらに高速です。
// Total count for pagination
int totalCount = await _db.Products
.Where(p => p.IsActive)
.CountAsync();
// Existence check — faster than Count > 0
bool hasExpensive = await _db.Products
.AnyAsync(p => p.Price > 1000m);
// SQL: IF EXISTS (SELECT 1 FROM ...) SELECT 1AddRangeによる一括挿入
AddRangeを使うと、複数のエンティティを一度に追加できます。EF CoreはINSERT文をより少ないラウンドトリップにまとめます。バッチサイズはプロバイダーのMaxBatchSizeで制御されます。
var newProducts = Enumerable.Range(1, 1000)
.Select(i => new Product { Name = $"Product {i}", Price = i * 1.5m })
.ToList();
_db.Products.AddRange(newProducts);
await _db.SaveChangesAsync();
// Batched: typically 100-1000 rows per round-tripデータベース関数向けのEF.Functions
EF.Functionsはデータベース固有の関数(LIKE、日付部分、全文検索)を提供します。これらはメモリ上で評価されるのではなく、EF CoreによってSQLに変換されます。
// LIKE pattern search (SQL-side)
var results = await _db.Products
.Where(p => EF.Functions.Like(p.Name, "%keyboard%"))
.AsNoTracking()
.ToListAsync();
// Date difference (SQL-side)
var recentOrders = await _db.Orders
.Where(o => EF.Functions.DateDiffDay(o.PlacedAt, DateTime.UtcNow) <= 7)
.ToListAsync();遅いクエリの診断
EF Coreのクエリロギングを有効にして、生成されたSQLを確認してください。開発環境でEnableSensitiveDataLogging()を使用すると、パラメーター値も確認できます。
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(connStr)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging() // dev only!
.EnableDetailedErrors()); // dev only!実践例:最適化された一覧エンドポイント
本番環境に対応した一覧エンドポイントでは、最大限のパフォーマンスを得るために、AsNoTracking、プロジェクション、インデックス、ページングを組み合わせます。
app.MapGet("/products", async (
AppDbContext db,
string? search,
int page = 1, int size = 20) =>
{
var query = db.Products
.AsNoTracking()
.Where(p => p.IsActive);
if (!string.IsNullOrEmpty(search))
query = query.Where(p => EF.Functions.Like(p.Name, $"%{search}%"));
var total = await query.CountAsync();
var items = await query
.OrderBy(p => p.Name)
.Skip((page - 1) * size).Take(size)
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
return Results.Ok(new { total, items });
});確認問題
通常のLINQクエリと比べて、EF.CompileAsyncQueryの主な利点は何ですか?
まとめ:コンパイル済みクエリとパフォーマンス
要点:
AsNoTracking():読み取り専用では変更追跡をスキップする — 最も手軽で高速な改善策EF.CompileAsyncQuery:頻繁に実行するクエリをデリゲートに事前コンパイルするSelectによるプロジェクション:必要な列だけを取得する- 適切なインデックス(
HasIndex)でクエリプランを大幅に改善する Skip/Takeでページングし、存在確認にはAnyAsyncを使用する- 開発環境でクエリロギングを有効にして、遅いSQLや予期しないSQLを見つける
よくある質問
「コンパイル済みクエリとパフォーマンス」レッスンは無料ですか?
はい。「コンパイル済みクエリとパフォーマンス」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「コンパイル済みクエリとパフォーマンス」で何を学びますか?
EF.CompileQuery、AsNoTracking、Selectによるプロジェクションを使って、クエリのパフォーマンスを高めます。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「コンパイル済みクエリとパフォーマンス」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Eager、Lazy、Explicit Loading
- Raw SQL、ストアドプロシージャと補間
- コンパイル済みクエリとパフォーマンス
- グローバルクエリフィルターと所有エンティティ