Start Debugging

EF Core 11 でのデータシードにおける HasData vs UseSeeding: どちらを使うべきか?

HasData は固定的でモデルが所有する参照データにのみ使います。EF Core 11 のそれ以外すべてには UseSeeding と UseAsyncSeeding を使います。判断を決定づけるルールとともに並べて比較します。

EF Core 11 でデータをシードするには、マイグレーション内でバージョン管理したい小さく固定的で決定論的な参照テーブル(国コード、通貨、enum のような検索行)にのみ HasData を使います。それ以外のすべてには UseSeedingUseAsyncSeeding を使います。データベース生成キー、計算値や非決定論的な値、条件分岐、ナビゲーションプロパティ、あるいはデータベースに既にあるものに依存する行を伴うものはすべてです。EF チームは HasData を “model-managed data” に改名しましたが、これはまさに汎用のシードツールとして使うことを思いとどまらせるためです。この記事では .NET 11、EF Core 11(Microsoft.EntityFrameworkCore 11.0)、C# 14 を使用します。

1 行だけ覚えるなら、HasData はモデルとマイグレーションの diff に参加し、UseSeeding は生きた DbContext に対して通常のコードとして実行されます。痛みを伴うシードのバグのほとんどは、後者が必要なときに前者を選ぶことから生じます。

機能マトリックス

これがあなたが求めてきた表です。各行は感覚ではなく実際の制約です。

機能HasData(model-managed data)UseSeeding / UseAsyncSeeding
設定場所OnModelCreating(モデル)DbContextOptionsBuilder
実行タイミングマイグレーション SQL 内(InsertData)Migrate/EnsureCreateddotnet ef database update
主キー手動で指定する必要があるデータベース生成キーで問題ない
非決定論的な値禁止(ビルドごとに再 diff)許可(Guid.NewGuid()DateTime.UtcNow)
ナビゲーションプロパティ不可、外部キーのみ可、グラフ全体の挿入
条件分岐不可可、if は自分で書く
外部呼び出し / 変換不可可(ハッシュ化、HTTP、ファイル読み取り)
冪等性自動(EF が diff)存在チェックを自分で書く
バージョン管理に記録されるはい、マイグレーションスナップショット内いいえ、起動時コードです
既存行の更新はい、マイグレーション diff 経由更新を書いた場合のみ
利用可能になったバージョンEF Core 1.0(HasData だった)EF Core 9、EF Core 11 で現行

何より重要な行は「非決定論的な値」です。HasData はビルドごとにモデルのスナップショットと比較されるため、シード内の DateTime.UtcNowGuid.NewGuid() はモデルが毎回変更されたように見せ、EF は PendingModelChangesWarning を投げるか、無意味なマイグレーションを延々と少しずつ生成します。これがほとんどの人がぶつかる壁で、たいてい EF Core 6 から EF Core 11 へのマイグレーションの最中に起こります。

HasData を選ぶとき

HasData は、データが本当にスキーマの意味の一部であるときに正しいツールです。頭の中でのテスト: これらの行を定数にハードコードしても平気で、ほとんど変わらないでしょうか?

// .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<OrderStatus>().HasData(
        new OrderStatus { Id = 1, Name = "Pending" },
        new OrderStatus { Id = 2, Name = "Shipped" },
        new OrderStatus { Id = 3, Name = "Delivered" });
}

キーは明示的に設定する必要があり、値は決定論的でなければなりません。Guid.NewGuid()DateTime.Now もナビゲーションプロパティも不可です(外部キー列を直接設定します)。そのいずれかを破ると、HasData はビルドごとにあなたと戦います。

UseSeeding と UseAsyncSeeding を選ぶとき

UseSeeding は EF Core 11 で推奨される汎用メカニズムです。生きた DbContext を持つ普通のアプリケーションコードなので、HasData の限界は単に存在しません。次のいずれかが当てはまるときは常にこれを使います:

// .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
services.AddDbContext<AppDbContext>(options =>
    options
        .UseSqlServer(connectionString)
        .UseAsyncSeeding(async (context, _, ct) =>
        {
            if (!await context.Set<User>().AnyAsync(u => u.Email == "admin@example.com", ct))
            {
                context.Set<User>().Add(new User
                {
                    Email = "admin@example.com",
                    PasswordHash = PasswordHasher.Hash("change-me"), // runtime transform
                    CreatedAt = DateTime.UtcNow                        // non-deterministic
                });
                await context.SaveChangesAsync(ct);
            }
        })
        .UseSeeding((context, _) =>
        {
            if (!context.Set<User>().Any(u => u.Email == "admin@example.com"))
            {
                context.Set<User>().Add(new User
                {
                    Email = "admin@example.com",
                    PasswordHash = PasswordHasher.Hash("change-me"),
                    CreatedAt = DateTime.UtcNow
                });
                context.SaveChanges();
            }
        }));

落とし穴はほとんどの人が見落とすものです。両方のオーバーロードを実装する必要があり、存在チェックはあなたの責任です。EF Core のツール(dotnet ef database update)は同期版の UseSeeding のみを呼び出し、実行時の経路は UseAsyncSeeding を呼び出します。一方を実装してもう一方を実装しないと、忘れた経路からのシードは静かに何もしません。完全なメカニズムについては UseSeeding と UseAsyncSeeding でデータをシードする方法を参照してください。

ディスク上で実際に起こること

違いを見る最も明確な方法は、それぞれが何を生成するかを見ることです。

HasData はマイグレーションへと具現化します。dotnet ef migrations add SeedStatuses の後、生成された Up メソッドには SQL の形をした呼び出しが含まれます:

// .NET 11, EF Core 11 generated migration
migrationBuilder.InsertData(
    table: "OrderStatuses",
    columns: new[] { "Id", "Name" },
    values: new object[,]
    {
        { 1, "Pending" },
        { 2, "Shipped" },
        { 3, "Delivered" }
    });

データは今やバージョン管理されたアーティファクトです。マイグレーションのトランザクション内で一度だけ実行され、EF はそれをモデルスナップショットで追跡するので、後の編集は UpdateData を生成します。非決定論的な値がここで毒になるのはまさにこのためです。スナップショットは決して一致せず、EF はモデルがビルドごとに変わったと信じてしまうのです。

UseSeeding はディスク上に何も生成しません。スキーマが最新になった後、MigrateEnsureCreated、または dotnet ef database update のたびに発火するコールバックで、マイグレーションが適用されなかった実行も含みます。無条件で実行されるため、存在チェックは任意の片付け作業ではなく、あなたと重複行の間に立つ唯一のものです。EF Core 11 はマイグレーションに使うのと同じマイグレーションロック機構でコールバックを保護するので、同時に起動する 2 つのアプリインスタンスが両方ともシードを並行実行することはありませんが、ロックは欠けている if を冪等にはしません。

あなたの代わりに決める落とし穴

いくつかの制約は好みを完全に上書きします。次のいずれかが当てはまれば、判断はあなたの代わりに決まっています:

1 つのアプリで両方を使うのはまったく問題ありません。よくある健全な分割: スキーマが機能できない 3 つの検索テーブルには HasData、デモデータ、デフォルト管理者、テナント固有のものには UseSeeding。これらは異なる段階で実行されるので衝突しません。

推奨、再び

EF Core 11 ではデフォルトで UseSeedingUseAsyncSeeding を使います。これらは推奨メカニズムで、実際のアプリケーションが本当にシードする動的でキー生成され条件付きのデータを扱い、マイグレーション履歴を静かに破壊するのではなく大きな音を立てて失敗します。HasData は、それが改名されて担うことになった狭いケースのために取っておきます。手で割り当てたキーを持ち、スキーマとともに本当にバージョン管理し diff したい、小さく固定的で決定論的な参照データです。

避けるべき罠は歴史的なデフォルトです。長年 HasData は唯一の組み込み選択肢だったので、コードベースは反射的にそれに手を伸ばし、その後ファントムマイグレーションと PendingModelChangesWarning に溺れました。EF Core 11 でゼロから始めるなら、その本能を逆にしてください。まず UseSeedingHasData はデータがモデルに属する理由を言葉にできるときだけです。records ベースのエンティティを保守しているなら、同じ分割がきれいに当てはまります。records は EF Core 11 で問題なく機能するので、どちらのメカニズムでも同様です。

関連

出典

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る