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-migration/dotnet ef database updateが安定実行できる状態にする
まず理解したい「デザイン時」の仕組み
EF Core のツール(PMC の Add-Migration/CLI の dotnet ef)は、デザイン時(Design-time)に DbContext を生成します。生成手順は次の優先順です。
IDesignTimeDbContextFactory<TContext>実装があればそれを呼ぶ- なければ Startup(実行)プロジェクトを起動して DI コンテナから
TContextを解決する - それも失敗したら コンストラクタ探索(
DbContextOptions<TContext>を受け取るもの 等)を試みる
本記事のエラーは 2 の段で失敗していることを示しています。つまり「ツールが起動したホストの DI に DbContextOptions<TContext> が登録されていない」状態です。
主な原因と解決策(俯瞰表)
| 原因 | 具体的な症状 | 解決策 |
|---|---|---|
| ① DbContextOptions が DI に登録されていない | DbContextOptions<YourDbContext> が解決できず例外 | Startup(デザイン時に起動される)プロジェクトの Program.cs で services.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 で明示 |
最短で直す・チェックリスト
- EF Core の設計時パッケージ(
Microsoft.EntityFrameworkCore.Design)が DbContext を含むプロジェクトに入っているか確認 - 実行(Startup)プロジェクトの
Program.csにAddDbContext<TContext>があることを確認 - CLI/PMC で
-Projectと-StartupProjectを明示(どちらも相対パス可能) - ビルドが成功する状態を担保(
dotnet buildで OK) - まだ失敗するなら
IDesignTimeDbContextFactory<T>を実装して流れを掌握
手順サンプル(SQL Server・王道パターン)
- ClassLibrary プロジェクトを作成(例:
MyLib) - NuGet に
Microsoft.EntityFrameworkCore.SqlServer/Design/(PMC を使うなら)Toolsを追加 - モデルと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<MyDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
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<User>()
.HasIndex(x => x.Name)
.HasDatabaseName("IX_User_Name");
}
}
- 実行(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<MyDbContext>(options =>
options.UseSqlServer(cs)); // ここでプロバイダーと接続文字列を確定
var app = builder.Build();
app.MapGet("/", () => "OK");
app.Run();
- マイグレーション作成
dotnet ef migrations add Init -Project MyLib -StartupProject MyApp
- データベース更新
dotnet ef database update -Project MyLib -StartupProject MyApp
この手順で Startup 側での登録と Context 側のフォールバックを併用しているため、デザイン時も実行時も安定します。特に OnConfiguring の if (!optionsBuilder.IsConfigured) ガードは二重設定事故を防ぐ要です。
IDesignTimeDbContextFactory<T> を実装して完全制御
ライブラリ単体でマイグレーションを生成したい、起動ホストに依存したくない場合は IDesignTimeDbContextFactory が最有力です。ツールはまずこのファクトリを探し、見つかればそれで Context を作ります。
// MyLib/DesignTime/MyDbContextFactory.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<MyDbContext>();
// 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<MyDbContext>(opt =>
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<MyDbContext>(opt =>
opt.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
// どこかのサービスで
public class UserService
{
private readonly IDbContextFactory<MyDbContext> _factory;
public UserService(IDbContextFactory<MyDbContext> factory) => _factory = factory;
public async Task<User?> FindAsync(int id)
{
await using var db = await _factory.CreateDbContextAsync();
return await db.Users.FindAsync(id);
}
}
この構成でもマイグレーションは Startup に AddDbContextFactory が登録されていれば問題なく動作します。
実運用での安全装備(ログ/検証)
- 接続先の可視化:デザイン時と実行時で別 DB を使うと事故の元。
IOptionsMonitor<T>などで最終的な接続文字列をログに出し、EnvironmentName(Development/Staging/Production)と合わせて記録します。 - 詳細ログ(開発限定):
options.EnableSensitiveDataLogging() .EnableDetailedErrors(); - マイグレーション差分のレビュー:
dotnet ef migrations add直後に生成ファイルを必ずレビューし、不要・過剰な差分を手修正してからコミットします。
CI/CD と環境切替のベストプラクティス
- 環境変数で上書き:デザイン時ファクトリは
MYAPP_CSなどの環境変数を優先。パイプラインで安全に注入できます。 - ライブラリ単体での生成:
IDesignTimeDbContextFactory採用で、Web アプリに依存せずにマイグレーションを作成可能(モノレポで特に有効)。 - 整合したバージョン:EF パッケージ群とプロバイダーを一括で上げ下げ。パッチ同士でも差異が出る場合は「全部そろえる」が鉄則。
トラブル再現→解消の実例(ケーススタディ)
症状:dotnet ef migrations add Init -p MyLib -s MyApp で「Unable to resolve service for type DbContextOptions<MyDbContext>」。
調査:
Program.csを確認 →AddDbContext<MyDbContext>の記述がないOnConfiguringにはUseSqlServerがあるがIsConfiguredガードなし → 二重設定の可能性も
対策:
Program.csにAddDbContext追加(接続文字列はappsettings.json管理)OnConfiguringにif (!optionsBuilder.IsConfigured)を追加- 再実行 → 成功。データベース作成も通過
よくある質問(FAQ)
Q. ライブラリに DbContext を置くのは良い設計? A. 複数アプリから共有する場合やドメイン層を分離したい場合は有効です。設計時生成の観点からは IDesignTimeDbContextFactory を同梱するのが最も安定します。 Q. OnConfiguring と AddDbContext のどちらを信頼すべき? 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 構成でも 「いつでも通るマイグレーション」 を実現できます。日々の開発を止めないための基礎体力として、ぜひプロジェクトに取り入れてください。
付録:トラブルシュート・フローチャート
- ビルド通る? → NO: まず
dotnet build - Startup 指定した?(
-s/-StartupProject) - Program.cs に AddDbContext ある?
- OnConfiguring のガードは?(
IsConfigured) - バージョン揃ってる?(EF パッケージ群とプロバイダー)
- IDesignTimeDbContextFactory 実装した?

コメント