EF Core 8 × .NET 8:ClassLibraryで出る「Unable to create a ‘DbContext’」「Unable to resolve service for DbContextOptions」エラーの完全解決ガイド

EF Core 8 と .NET 8 の組み合わせで ClassLibrary(ライブラリ)に DbContext を置く構成は一般的ですが、マイグレーション実行時に Unable to create a 'DbContext'…Unable to resolve service for type 'DbContextOptions<TContext>' が出て足止めになるケースが少なくありません。本記事は原因の切り分けから確実に通る実装テンプレートまで、実務で使える対処法を体系的にまとめた決定版です。

目次

前提とゴール

  • 対象:.NET 8(C# 12)/EF Core 8 を利用し、DbContext を ClassLibrary に置く構成の開発者
  • 想定エラー: Unable to create a 'DbContext' of type '…'. Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions<YourDbContext>'
  • ゴール:上記エラーを再発させず、add-migrationdotnet ef database update が安定実行できる状態にする

まず理解したい「デザイン時」の仕組み

EF Core のツール(PMC の Add-Migration/CLI の dotnet ef)は、デザイン時(Design-time)に DbContext を生成します。生成手順は次の優先順です。

  1. IDesignTimeDbContextFactory<TContext> 実装があればそれを呼ぶ
  2. なければ Startup(実行)プロジェクトを起動して DI コンテナから TContext を解決する
  3. それも失敗したら コンストラクタ探索DbContextOptions<TContext> を受け取るもの 等)を試みる

本記事のエラーは 2 の段で失敗していることを示しています。つまり「ツールが起動したホストの DI に DbContextOptions<TContext> が登録されていない」状態です。

主な原因と解決策(俯瞰表)

原因具体的な症状解決策
① DbContextOptions が DI に登録されていないDbContextOptions<YourDbContext> が解決できず例外Startup(デザイン時に起動される)プロジェクトの Program.csservices.AddDbContext<YourDbContext>(…) を登録 CLI では -StartupProject <実行プロジェクト> を明示
② DbContext に適切なコンストラクタが無いパラメータなしコンストラクタのみ/そもそも無しpublic YourDbContext(DbContextOptions<YourDbContext> options) : base(options) {} を追加
③ 複数スタートアッププロジェクトがあるどのプロジェクトを起動すべきか判断できず失敗Add-Migration Initial -Project <DbContextを含む> -StartupProject <実行> を必ず指定
④ パッケージのバージョン不整合プロバイダーと EF Core のメジャー/マイナーがズレて実行時例外解決:Microsoft.EntityFrameworkCore.* と使用プロバイダー(例:Pomelo.MySql、Npgsql、Sqlite 等)を同系統のバージョンで揃える
⑤ dotnet-ef ツール未インストール/ビルド失敗dotnet ef が見つからない/ビルドエラーで中断dotnet tool install --global dotnet-ef 実行。ソリューション全体がビルド成功することを確認
⑥ 実行プロジェクトが最小ホストで DbContext を登録していないProgram.cs が最小 API 構成だが AddDbContext 呼び出しなしvar cs = builder.Configuration.GetConnectionString("Default"); builder.Services.AddDbContext<T>(o => o.UseXxx(cs)); を追加
⑦ OnConfiguring と DI の二重設定プロバイダーが二重に設定され例外/意図しない接続先OnConfiguring 内は if (!optionsBuilder.IsConfigured) ガードで二重設定を防止
⑧ 実行プロジェクトのビルド構成が対象フレームワークと不一致TargetFramework の違い等で起動不能ライブラリも実行プロジェクトも net8.0 系で揃える
(関連トラブル)ID プロパティ名が慣例から外れている主キー検出が期待通りでない/マイグレーション内容が不自然Id または <エンティティ名>Id に合わせるか、[Key] 属性/HasKey で明示

最短で直す・チェックリスト

  1. EF Core の設計時パッケージ(Microsoft.EntityFrameworkCore.Design)が DbContext を含むプロジェクトに入っているか確認
  2. 実行(Startup)プロジェクトの Program.csAddDbContext<TContext> があることを確認
  3. CLI/PMC で -Project-StartupProject を明示(どちらも相対パス可能)
  4. ビルドが成功する状態を担保(dotnet build で OK)
  5. まだ失敗するなら IDesignTimeDbContextFactory<T> を実装して流れを掌握

手順サンプル(SQL Server・王道パターン)

  1. ClassLibrary プロジェクトを作成(例:MyLib
  2. NuGet に Microsoft.EntityFrameworkCore.SqlServerDesign/(PMC を使うなら)Tools を追加
  3. モデルDbContext を実装
// MyLib/Entities/User.cs
public class User
{
    public int Id { get; set; }             // 慣例:Id または UserId
    public string Name { get; set; } = string.Empty;
}

// MyLib/MyDbContext.cs
using Microsoft.EntityFrameworkCore;

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions&lt;MyDbContext&gt; options) : base(options) { }

    public DbSet&lt;User&gt; Users =&gt; Set&lt;User&gt;();

    protected override void OnConfiguring(DbContextOptionsBuilder b)
    {
        // デザイン時に Startup 側で未設定の場合のフォールバック
        if (!b.IsConfigured)
        {
            b.UseSqlServer("Server=.;Database=Sample;Trusted_Connection=True;TrustServerCertificate=True;");
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 例:制約・インデックス
        modelBuilder.Entity&lt;User&gt;()
            .HasIndex(x =&gt; x.Name)
            .HasDatabaseName("IX_User_Name");
    }
}
  1. 実行(Startup)プロジェクトProgram.cs で登録
// MyApp/Program.cs (.NET 8 Minimal hosting)
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// appsettings.json 側に "ConnectionStrings:Default" を用意しておくと安全
var cs = builder.Configuration.GetConnectionString("Default");

builder.Services.AddDbContext&lt;MyDbContext&gt;(options =&gt;
    options.UseSqlServer(cs));  // ここでプロバイダーと接続文字列を確定

var app = builder.Build();
app.MapGet("/", () =&gt; "OK");
app.Run();
  1. マイグレーション作成
dotnet ef migrations add Init -Project MyLib -StartupProject MyApp
  1. データベース更新
dotnet ef database update -Project MyLib -StartupProject MyApp

この手順で Startup 側での登録Context 側のフォールバックを併用しているため、デザイン時も実行時も安定します。特に OnConfiguringif (!optionsBuilder.IsConfigured) ガードは二重設定事故を防ぐ要です。

IDesignTimeDbContextFactory<T> を実装して完全制御

ライブラリ単体でマイグレーションを生成したい、起動ホストに依存したくない場合は IDesignTimeDbContextFactory が最有力です。ツールはまずこのファクトリを探し、見つかればそれで Context を作ります。

// MyLib/DesignTime/MyDbContextFactory.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

public class MyDbContextFactory : IDesignTimeDbContextFactory&lt;MyDbContext&gt;
{
    public MyDbContext CreateDbContext(string[] args)
    {
        var builder = new DbContextOptionsBuilder&lt;MyDbContext&gt;();

        // 1. 引数 or 環境変数優先(CI/CD 向け)
        var cs = Environment.GetEnvironmentVariable("MYAPP_CS");

        // 2. それもなければ開発用のローカル接続
        cs ??= "Server=.;Database=Sample;Trusted_Connection=True;TrustServerCertificate=True;";

        builder.UseSqlServer(cs);
        return new MyDbContext(builder.Options);
    }
}

このファクトリを置くと、-StartupProject 指定がなくても ライブラリ単体でマイグレーションを切れます。

複数プロジェクト構成の正しいコマンド例

典型的なソリューション構成を前提に、PMC/CLI それぞれの確実な指定例を示します。

MySolution/
  src/
    MyApp/           // 実行(Startup)プロジェクト
    MyLib/           // DbContext とエンティティ
  • CLI: dotnet ef migrations add Init \ -p src/MyLib/ -s src/MyApp/ \ -o Migrations
  • PMC: Add-Migration Init -Project src/MyLib -StartupProject src/MyApp -OutputDir Migrations

OnConfiguring と AddDbContext の使い分け

パターン利点注意点おすすめ度
Startup 側でのみ AddDbContext構成の一元化、接続切り替えが容易Startup 指定がないとデザイン時に失敗しやすい◎(ASP.NET Core アプリ主体ならこれ)
Context 側 OnConfiguring のみライブラリ単体で自己完結実行時の差し替えが難しく、テストで不便△(小規模ツールや検証用途)
両方+IsConfigured ガードデザイン時/実行時の双方で強いガードを忘れると二重設定で例外◎(本記事の推奨)

プロバイダー別メモ(EF Core 8)

  • SQL Server
    • Microsoft.EntityFrameworkCore.SqlServer
    • ローカル開発では TrustServerCertificate=True を付けると証明書警告を回避できます(本番では適切な証明書を使用)
  • MySQL / MariaDB
    • 代表例:Pomelo.EntityFrameworkCore.MySql
    • EF Core とプロバイダーのメジャー/マイナー整合を厳守。ズレると設計時にクラッシュすることがあります
    • 不一致を疑ったら 全パッケージのバージョンをそろえるのが最短の解決策
  • PostgreSQL
    • Npgsql.EntityFrameworkCore.PostgreSQL
    • 一部の拡張型はマイグレーションで追加設定が必要になるため、OnModelCreating で明示
  • SQLite
    • テストに最適。Data Source=app.db のように簡潔に書けます
    • 並列テストではファイルロックに注意

実用テンプレート:設定ファイル + 依存性注入

接続文字列を appsettings.json で管理し、デザイン時も実行時も同一の値を使うテンプレートです。

{
  "ConnectionStrings": {
    "Default": "Server=.;Database=Sample;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}
// MyApp/Program.cs
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext&lt;MyDbContext&gt;(opt =&gt;
    opt.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

var app = builder.Build();
app.Run();
// MyLib/MyDbContext.cs
protected override void OnConfiguring(DbContextOptionsBuilder b)
{
    if (!b.IsConfigured) // Startup 未設定時のフォールバック
        b.UseSqlServer("Server=.;Database=Sample;Trusted_Connection=True;TrustServerCertificate=True;");
}

確実に動くコマンドの書き方(覚え書き)

  • 最初のマイグレーション: dotnet ef migrations add Initial \ -p src/MyLib -s src/MyApp -o Migrations
  • DB 更新: dotnet ef database update -p src/MyLib -s src/MyApp
  • (PMC)パッケージ マネージャー コンソール: Add-Migration Initial -Project src/MyLib -StartupProject src/MyApp -OutputDir Migrations Update-Database -Project src/MyLib -StartupProject src/MyApp

よくある例外と処方箋(コピペで効く)

例外メッセージ(抜粋)原因直し方
Unable to resolve service for type 'DbContextOptions<T>'DI に登録がない/Startup 指定ミスAddDbContext を Startup に追加し、-StartupProject を明示
No parameterless constructor was found on 'TContext'適切なコンストラクタなしpublic TContext(DbContextOptions<TContext> options) を追加
More than one DbContext was found複数 Context で曖昧-Context YourDbContext を追加指定
Build failed.ソリューションがビルド不可dotnet build でエラー修正後に再実行
マイグレーションはできるがテーブルが想定外主キー推論が外れたプロパティ名を慣例に合わせるか、[Key]HasKey で明示

テストや分離シナリオで便利:AddDbContextFactory

Web アプリのスコープとは独立に Context を生成したいときは AddDbContextFactory<T> が便利です。

builder.Services.AddDbContextFactory&lt;MyDbContext&gt;(opt =&gt;
    opt.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

// どこかのサービスで
public class UserService
{
    private readonly IDbContextFactory&lt;MyDbContext&gt; _factory;
    public UserService(IDbContextFactory&lt;MyDbContext&gt; factory) =&gt; _factory = factory;

    public async Task&lt;User?&gt; FindAsync(int id)
    {
        await using var db = await _factory.CreateDbContextAsync();
        return await db.Users.FindAsync(id);
    }
}

この構成でもマイグレーションは StartupAddDbContextFactory が登録されていれば問題なく動作します。

実運用での安全装備(ログ/検証)

  • 接続先の可視化:デザイン時と実行時で別 DB を使うと事故の元。IOptionsMonitor<T> などで最終的な接続文字列をログに出し、EnvironmentNameDevelopment/Staging/Production)と合わせて記録します。
  • 詳細ログ(開発限定): options.EnableSensitiveDataLogging() .EnableDetailedErrors();
  • マイグレーション差分のレビューdotnet ef migrations add 直後に生成ファイルを必ずレビューし、不要・過剰な差分を手修正してからコミットします。

CI/CD と環境切替のベストプラクティス

  1. 環境変数で上書き:デザイン時ファクトリは MYAPP_CS などの環境変数を優先。パイプラインで安全に注入できます。
  2. ライブラリ単体での生成IDesignTimeDbContextFactory 採用で、Web アプリに依存せずにマイグレーションを作成可能(モノレポで特に有効)。
  3. 整合したバージョン:EF パッケージ群とプロバイダーを一括で上げ下げ。パッチ同士でも差異が出る場合は「全部そろえる」が鉄則。

トラブル再現→解消の実例(ケーススタディ)

症状dotnet ef migrations add Init -p MyLib -s MyApp で「Unable to resolve service for type DbContextOptions<MyDbContext>」。

調査

  1. Program.cs を確認 → AddDbContext<MyDbContext> の記述がない
  2. OnConfiguring には UseSqlServer があるが IsConfigured ガードなし → 二重設定の可能性も

対策

  1. Program.csAddDbContext 追加(接続文字列は appsettings.json 管理)
  2. OnConfiguringif (!optionsBuilder.IsConfigured) を追加
  3. 再実行 → 成功。データベース作成も通過

よくある質問(FAQ)

Q. ライブラリに DbContext を置くのは良い設計? A. 複数アプリから共有する場合やドメイン層を分離したい場合は有効です。設計時生成の観点からは IDesignTimeDbContextFactory を同梱するのが最も安定します。 Q. OnConfiguringAddDbContext のどちらを信頼すべき? A. 実行環境ごとの切り替えが必要なら AddDbContext を主とし、OnConfiguring はフォールバックに限定しましょう。 Q. 主キーが idUser のような名前でも動く? A. 動きますが、EF の慣例から外れると推論が効かず、思わぬ差分が出ることがあります。Id/UserId か、明示的に [Key] を付けるのが安全です。 Q. dotnet-ef が見つかりません。 A. dotnet tool install --global dotnet-ef で導入し、dotnet tool update --global dotnet-ef で更新してください。PATH 設定も確認を。

チェック用スニペット集

Context の最小実装(EF8)

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
    public DbSet<User> Users => Set<User>();

```
protected override void OnConfiguring(DbContextOptionsBuilder b)
{
    if (!b.IsConfigured) b.UseSqlServer("Server=.;Database=Sample;Trusted_Connection=True;TrustServerCertificate=True;");
}
```

} 

PMC で複数 Context がある場合

Add-Migration Init -Project MyLib -StartupProject MyApp -Context MyDbContext

CLI で Context を指定

dotnet ef migrations add Init -p MyLib -s MyApp --context MyDbContext

まとめ

  • 本質:設計時に EF ツールが DbContextOptions<T> を解決できていない
  • 直す順番:AddDbContext を Startup に登録 → -StartupProject を明示 → それでもダメなら IDesignTimeDbContextFactory で掌握
  • 再発防止:バージョン整合・二重設定防止(IsConfigured)・マイグレーション差分レビュー

ここまで整えれば、.NET 8 + EF Core 8 の ClassLibrary 構成でも 「いつでも通るマイグレーション」 を実現できます。日々の開発を止めないための基礎体力として、ぜひプロジェクトに取り入れてください。

付録:トラブルシュート・フローチャート

  1. ビルド通る? → NO: まず dotnet build
  2. Startup 指定した?-s / -StartupProject
  3. Program.cs に AddDbContext ある?
  4. OnConfiguring のガードは?IsConfigured
  5. バージョン揃ってる?(EF パッケージ群とプロバイダー)
  6. IDesignTimeDbContextFactory 実装した?

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次