mcpField
E
entity-framework-core

entity-framework-core

github.com/managedcodeデータ・データベース49677 回インストール1 日前に更新
リポジトリ

entity-framework-core について

entity-framework-coreは、EF Coreのデータアクセスを構築・レビューする開発スキルです。DbContext、クエリ、マイグレーション、性能に関する判断を、データベースアプリケーション向けのパターンとアンチパターンで支援します。

主な機能

  1. 01DbContextのライフサイクルと設定の指針
  2. 02クエリの追跡・読み込み・射影パターン
  3. 03マイグレーションとスキーマ変更の実践
  4. 04性能・インデックス・同時実行の指針
  5. 05生成SQLとN+1クエリの確認

活用シーン

  1. 01EF Coreモデルとクエリの設計
  2. 02マイグレーションとDbContext利用のレビュー
  3. 03遅いクエリやN+1の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/managedcode/dotnet-skills --skill entity-framework-core

インストール後、Claude Code で/entity-framework-coreを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

manifest.json
references/
anti-patterns.md
patterns.md

詳細

Entity Framework Core

発動条件

  • DbContext、マイグレーション、モデル構成、または EF クエリを扱う場合
  • 追跡、読み込み、パフォーマンス、またはトランザクションの動作をレビューする場合
  • EF6 またはカスタムリポジトリから EF Core にデータアクセスを移行する場合
  • 遅いデータベースクエリを最適化する場合

ドキュメント

リファレンス
  • patterns.md - クエリパターン、追跡戦略、読み込み戦略、プロジェクション、コンパイル済みクエリ、ページネーション、テンポラルテーブル
  • anti-patterns.md - N+1 クエリ、大きなコンテキスト、汎用リポジトリ、インデックス不足など、EF Core でよくある間違い

ワークフロー

  1. 新規開発では EF Core を優先します。文書化された制約があり、Dapper または生 SQL が必要な場合を除きます
  2. DbContext の有効期間はスコープにします — 作業単位に合わせます
  3. クエリの変換を確認します — 生成された SQL を確認し、N+1 を回避します
  4. マイグレーションを重要な成果物として扱います — 使い捨てにせず、レビュー可能な状態にします
  5. プロバイダーの動作を慎重に考慮します — プロバイダー間で共通点はありますが、同一ではありません
  6. クエリを調査して検証します — 頭の中だけのデータモデルに頼りません

現在の上流プロジェクトの注記

  • EF Core v10.0.11 はサービスリリースです。Azure SQL の互換性レベル 170 における JSON 変換を修正し、ネストされた OPENJSON プロジェクションで AS JSON が維持されるようになりました。また、.NET 11 で追加された MemoryExtensions.Min/Max オーバーロードとの互換性も保たれます。アップグレード後は、プロバイダーを使用した JSON クエリを再実行し、リポジトリで選択されているターゲットフレームワークに対してコンパイルしてください。
  • 2026 年 8 月時点の EF Core と EF6 の比較は、移行を判断する際の最初の参照先です。EF Core は現在も開発が続くクロスプラットフォームのスタックですが、EF6 専用の EDMX/ObjectContext を多用するコードは、機能を洗い出し、データベースを使用した同等性テストを行わずに移行しないでください。

DbContext のパターン

基本的な構成
public class AppDbContext : DbContext
{
    public DbSet<Product> Products => Set<Product>();
    public DbSet<Order> Orders => Set<Order>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
    }
}

// Entity Configuration (Fluent API)
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> builder)
    {
        builder.HasKey(p => p.Id);
        builder.Property(p => p.Name).HasMaxLength(200).IsRequired();
        builder.HasIndex(p => p.Sku).IsUnique();
        builder.HasMany(p => p.OrderItems).WithOne(oi => oi.Product);
    }
}
DI への登録
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .EnableSensitiveDataLogging()  // Dev only
           .EnableDetailedErrors());      // Dev only

// Or with pooling (better performance)
builder.Services.AddDbContextPool<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

クエリパターン

読み取り専用には AsNoTracking を使用する
// Bad - tracks entities unnecessarily
var products = await db.Products.ToListAsync();

// Good - no tracking overhead
var products = await db.Products
    .AsNoTracking()
    .ToListAsync();
DTO にプロジェクションする
// Bad - loads entire entity graph
var orders = await db.Orders
    .Include(o => o.Items)
    .Include(o => o.Customer)
    .ToListAsync();

// Good - loads only needed data
var orders = await db.Orders
    .Select(o => new OrderDto
    {
        Id = o.Id,
        CustomerName = o.Customer.Name,
        ItemCount = o.Items.Count,
        Total = o.Items.Sum(i => i.Price)
    })
    .ToListAsync();
N+1 クエリを回避する
// Bad - N+1 problem
foreach (var order in orders)
{
    var items = await db.OrderItems
        .Where(i => i.OrderId == order.Id)
        .ToListAsync();
}

// Good - eager loading
var orders = await db.Orders
    .Include(o => o.Items)
    .ToListAsync();

// Good - split query for large graphs
var orders = await db.Orders
    .Include(o => o.Items)
    .AsSplitQuery()
    .ToListAsync();
コンパイル済みクエリ(EF Core 9)
// Pre-compiled for frequently used queries
private static readonly Func<AppDbContext, int, Task<Product?>> GetProductById =
    EF.CompileAsyncQuery((AppDbContext db, int id) =>
        db.Products.FirstOrDefault(p => p.Id == id));

// Usage
var product = await GetProductById(db, productId);

マイグレーションのパターン

マイグレーションの作成
# Add migration
dotnet ef migrations add AddProductIndex

# Apply to database
dotnet ef database update

# Generate SQL script
dotnet ef migrations script --idempotent -o migrate.sql
データマイグレーション
public partial class AddProductIndex : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateIndex(
            name: "IX_Products_Sku",
            table: "Products",
            column: "Sku",
            unique: true);

        // Data migration (if needed)
        migrationBuilder.Sql(@"
            UPDATE Products
            SET NormalizedName = UPPER(Name)
            WHERE NormalizedName IS NULL");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropIndex(
            name: "IX_Products_Sku",
            table: "Products");
    }
}

回避すべきアンチパターン

アンチパターン問題点より良い方法
ToList() の後にフィルターするすべてのデータをメモリに読み込むクエリ内でフィルターする
リクエストごとに複数の DbContext を使うトランザクションの問題が発生するスコープの有効期間にする
あらゆる場所で遅延読み込みを使うN+1 クエリが発生する明示的に Include する
汎用リポジトリのラッパークエリの柔軟性が失われるDbContext を直接使う
生成された SQL を無視するパフォーマンスの問題が見えなくなるログに記録してレビューする
ループ内で SaveChanges() を呼ぶ往復回数が増えるバッチ処理してから保存する

パフォーマンスのベストプラクティス

  1. 頻繁にクエリされる列にインデックスを付けます:

    builder.HasIndex(p => p.CreatedAt);
    builder.HasIndex(p => new { p.Category, p.Status });
    
  2. ページネーションを使います:

    var page = await db.Products
        .OrderBy(p => p.Id)
        .Skip(pageSize * pageNumber)
        .Take(pageSize)
        .ToListAsync();
    
  3. 更新をバッチ処理します(EF Core 7 以降):

    await db.Products
        .Where(p => p.Category == "Obsolete")
        .ExecuteDeleteAsync();
    
    await db.Products
        .Where(p => p.Category == "Sale")
        .ExecuteUpdateAsync(p => p.SetProperty(x => x.Price, x => x.Price * 0.9m));
    
  4. ネットワークの往復を最小限に抑えます:

    // Bad - 3 roundtrips
    var product = await db.Products.FindAsync(id);
    var reviews = await db.Reviews.Where(r => r.ProductId == id).ToListAsync();
    var related = await db.Products.Where(p => p.Category == product.Category).ToListAsync();
    
    // Good - 1 roundtrip
    var data = await db.Products
        .Where(p => p.Id == id)
        .Select(p => new
        {
            Product = p,
            Reviews = p.Reviews,
            Related = db.Products.Where(r => r.Category == p.Category).Take(5)
        })
        .FirstOrDefaultAsync();
    

同時実行のパターン

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ConcurrencyCheck]
    public int Version { get; set; }

    // Or use RowVersion
    [Timestamp]
    public byte[] RowVersion { get; set; }
}

// Handle concurrency conflicts
try
{
    await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
    var entry = ex.Entries.Single();
    var databaseValues = await entry.GetDatabaseValuesAsync();
    // Resolve conflict...
}

提供するもの

  • ドメインに合致する EF Core のモデルとクエリ
  • より安全なマイグレーションと有効期間の管理
  • パフォーマンスを考慮したデータアクセスの判断
  • 適切なインデックス設定とクエリの最適化

検証項目

  • クエリの動作が意図どおりである(SQL ログを確認する)
  • マイグレーションがレビュー可能で、正しい
  • 一般的な処理経路に N+1 クエリがない
  • フィルターや並べ替えに使う列にインデックスがある
  • DbContext の有効期間が適切にスコープされている
  • 重要なエンティティの同時実行が適切に処理されている

コメント