「dotnet build は通るのに、VS Code の C# Dev Kit では補完もビルドも失敗する」。そんな “あるある” を再現構成とともに解剖し、なぜ起きるのか、どう直すのが実務的に正しいのかを、NuGet と設計運用の観点から掘り下げて解説します。
前提と現象の整理
ここで扱うのは次の構成です。
- ソリューションは 2 プロジェクト:.NET コンソール アプリ(スタートアップ)と クラス ライブラリ。
- ライブラリには
Pomelo.EntityFrameworkCore.MySqlを<PackageReference>で追加(このパッケージは 推移的(transitive)にMicrosoft.EntityFrameworkCoreを引き込む)。 - コンソール プロジェクトのコードに
using Microsoft.EntityFrameworkCore;を書く。 - C# Dev Kit 上では補完やビルドが失敗する一方、CLI の
dotnet buildではエラーにならない。
要望は「スタートアップ側の .csproj を汚さず(= 明示的に EF Core を追加せず)に、IDE 側のエラーを解消したい」。
なぜ起きる? ─ 設計時ビルドと推移的依存のギャップ
カギは Design-time Build(設計時ビルド) と Command-line Build(CLI の実ビルド) の違いにあります。C# Dev Kit はエディタ内の補完・型解決のために、プロジェクトを「設計時」に評価して参照解決を行います。一方、dotnet build は最終的な出力を得るために依存グラフ全体を解決してビルドします。
| 観点 | Design-time Build(C# Dev Kit) | dotnet build(CLI) |
|---|---|---|
| 目的 | 補完/エラー表示などIDE体験 | 本番と同等の成果物生成 |
| 依存解決 | 高速化優先。推移的依存がプロジェクト境界をまたぐ場合に取りこぼしが起こることがある | NuGet グラフを確定。推移的依存を正しく統合 |
| 根拠ファイル | obj/project.assets.json を設計時評価で参照 | dotnet restore で確定した project.assets.json を使用 |
| 再現頻度 | プロジェクト間参照 + 推移的依存 + IDEキャッシュの条件で散発 | 低い(理屈どおり通る) |
今回のケースでは、ライブラリが Microsoft.EntityFrameworkCore を推移的に持っているため、コンソールの設計時評価で EF Core の参照が見えず、using Microsoft.EntityFrameworkCore; が解決できない──という矛盾が発生しています。
最短で確実:アプリ側に明示的な PackageReference を足す
実務では、アプリケーションが直接利用する API は、スタートアップ側に明示参照を追加するのが最も堅牢です。これで設計時・実ビルドの双方で依存グラフが一致し、IDE の取りこぼしも消えます。
- コンソール プロジェクトに EF Core を明示追加:
dotnet add <ConsoleProject>.csproj package Microsoft.EntityFrameworkCore --version 8.0.3 dotnet restore - VS Code を再読み込み(または C# Dev Kit の言語サーバーを再起動)。
このアプローチの副次効果は大きく、次のようなメリットがあります。
- バージョン固定:アプリが使う API のバージョンを明示し、予期せぬ更新を防止。
- セキュリティ:古い版が混入しづらく、脆弱性修正を取りこぼしにくい。
- レビュー容易:PR で「なぜその API を使うのか」が
.csprojで見える。
「csproj を汚したくない」場合にできること
ポリシーや美観の理由でアプリ側にパッケージ行を増やしたくない、というケースもあります。IDE の不整合自体を確実に潰せるのは明示参照ですが、設計・運用を工夫することで「追加しなくても IDE エラーを出さない」構成に寄せることは可能です。
1) EF Core を 呼び出し側からは見えない抽象で包む
コンソールが EF Core の型や拡張メソッドに直接触れなければ、using Microsoft.EntityFrameworkCore; は不要です。次のように、リポジトリ/UoWなどの抽象をクラス ライブラリ側に置き、コンソールは抽象だけを参照します。
// クラスライブラリ(EF 依存を閉じ込める)
public interface IUserRepository {
Task<User> FindAsync(Guid id);
}
public sealed class EfUserRepository : IUserRepository {
private readonly MyDbContext _db;
public EfUserRepository(MyDbContext db) => _db = db;
public Task<User> FindAsync(Guid id) => _db.Users.FindAsync(id).AsTask();
}
// コンソール(EF の using は不要)
var repo = host.Services.GetRequiredService();
var user = await repo.FindAsync(id);
コンソールが EF の API に触れなければ、IDE は EF の参照を要求しません。設計の分離としても健全です。
2) Central Package Management(Directory.Packages.props)を導入
中央パッケージ管理を使うと、バージョン定義を 1 か所に集約できます。アプリ側に <PackageReference> の 1 行は残りますが、バージョン記述は csproj に現れないため「見た目の汚れ」を最小化できます。
<!-- ルートの Directory.Packages.props -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>
</Project>
各プロジェクト側は次の 1 行だけで済みます。
<PackageReference Include="Microsoft.EntityFrameworkCore" /></code></pre>
<h3>3) Package Source Mapping で取得元を制御</h3>
<p><strong>Package Source Mapping</strong> は、特定のパッケージをどのフィードから取るかを厳密に制御する仕組みです。直接の IDE 不具合解消には寄与しませんが、<em>明示参照を導入する代わりに</em>供給元の統制・整合性を高めるのに有効です(大規模組織向け)。</p>
<pre><code><configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="contoso" value="https://nuget.contoso.example/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="Microsoft.EntityFrameworkCore*" />
<package pattern="Pomelo.EntityFrameworkCore.MySql" />
</packageSource>
<packageSource key="contoso">
<package pattern="Contoso.*" />
</packageSource>
</packageSourceMapping>
</configuration>
</code></pre>
</section>
<section>
<h2>補足:transitive 依存に頼りすぎない理由</h2>
<p>推移的依存は便利ですが、<strong>アプリが直接使う API</strong> については明示参照がベターです。その理由を整理します。</p>
<table>
<thead>
<tr>
<th>観点</th>
<th>推移的依存に任せる</th>
<th>アプリで明示参照</th>
</tr>
</thead>
<tbody>
<tr>
<td>バージョン決定</td>
<td>ライブラリ作者の「最小動作版」に引きずられがち</td>
<td>アプリの都合で固定・アップグレード可</td>
</tr>
<tr>
<td>セキュリティ</td>
<td>古い版が温存されやすい</td>
<td>更新判断を自分で握れる</td>
</tr>
<tr>
<td>IDE 体験</td>
<td>設計時ビルドの取りこぼしに遭遇しやすい</td>
<td>IDE と CLI のグラフが一致しやすい</td>
</tr>
<tr>
<td>チーム可視性</td>
<td>どの API を使うのか見えづらい</td>
<td>PR で露出し、レビューが効く</td>
</tr>
</tbody>
</table>
</section>
<section>
<h2>ミニ再現と確認コマンド</h2>
<p>最小構成で現象が起きるかを確かめる手順です。</p>
<ol>
<li>プロジェクトの作成:
<pre><code>dotnet new sln -n Demo
dotnet new classlib -n Data
dotnet new console -n App
dotnet sln Demo.sln add Data/Data.csproj App/App.csproj
dotnet add Data/Data.csproj package Pomelo.EntityFrameworkCore.MySql
dotnet add App/App.csproj reference Data/Data.csproj
App/Program.cs に using Microsoft.EntityFrameworkCore; を追加。 dotnet build で通るか確認(たいてい通る)。 VS Code + C# Dev Kit で開き、補完・ビルドを確認。
依存の見え方は次で確認できます。
dotnet list App/App.csproj package --include-transitive
ここに Microsoft.EntityFrameworkCore が現れていれば、CLI 側のグラフは通っています。IDE のみ失敗するなら、設計時評価の取りこぼし(またはキャッシュ)と判断できます。
IDE を回復させるチェックリスト
- リストアのやり直し:
dotnet restore、必要に応じてdotnet nuget locals all --clear。 - obj の破棄:
dotnet cleanまたはrm -r **/obj **/bin(OS に応じて)。 - ワークスペースの形:
.slnに両プロジェクトが含まれているか、VS Code はソリューション ルートで開いているか。 - 多重ターゲット:
TargetFrameworksを複数指定しているなら、一時的に単一にして症状が変わるか確認。 - Dev Kit の再起動:コマンドパレットから言語サーバーを再起動。
- 分析用ログ:出力パネルの C# / ログで設計時ビルドの失敗が出ていないか。
| 症状 | 想定される原因 | 即時チェック |
|---|---|---|
using Microsoft.EntityFrameworkCore が未解決 | 設計時の参照グラフに EF Core が載っていない | dotnet list package --include-transitive、obj/project.assets.json |
| CLI は通るが IDE だけ失敗 | IDE キャッシュ/設計時ビルドの取りこぼし | クリーン & リストア、言語サーバー再起動 |
| IDE でも CLI でも失敗 | 参照漏れ・バージョン競合 | dotnet restore -v diag で競合を特定 |
実務運用のベストプラクティス
1) 「使うなら明示参照」が基本線
特に EF Core のような基幹パッケージは、アプリが拡張メソッドや属性を直接使うことが多く、推移的依存に任せると IDE の不一致が表面化しやすいです。アプリ側に 1 行追加するだけで設計時の不安定要素を排除できます。
2) バージョンは中央集約、アプリは薄く
Directory.Packages.props を導入すると、バージョン管理・横展開が圧倒的に楽になります。CI では次の組み合わせが有効です。
dotnet list package --outdated:更新候補の把握dotnet list package --vulnerable --include-transitive:脆弱性検出RestorePackagesWithLockFileをtrue、dotnet restore --locked-mode:再現性の担保
3) 自動アップデートの導入
Dependabot や Renovate を使えば、パッチ更新やメジャー更新の検証を自動化できます。レビュー基準(互換性タグ、パッケージごとのポリシー)を明確にしておくと運用コストを抑えられます。
よくある誤解と落とし穴
- 「ライブラリが参照しているなら、アプリでも使えるはず」 ─ ランタイム的には流れても、コンパイル時に IDE がその参照を見落とすことがあり、補完やエラー表示に差が出ます。
- 「global using をライブラリに書けば、アプリも恩恵を受ける」 ─
global usingはプロジェクト内でのみ有効で、他プロジェクトには伝播しません。 - 「拡張メソッドはライブラリ側にあるから大丈夫」 ─ 呼び出し元のコンパイル時にも、拡張メソッドの定義アセンブリが解決できる必要があります。IDE が推移的参照を見落とすと候補に出ません。
結論:IDE の安定とセキュリティのために「明示」を選ぶ
今回の現象は、C# Dev Kit の設計時ビルドが推移的依存をプロジェクト境界越しに正しく解決できない場合に露呈する、IDE 特有のギャップです。最もシンプルで再現性の高い解決策は、アプリ側で EF Core を明示参照すること。さらに中央パッケージ管理でバージョン定義を集約すれば、csproj のスリム化と運用性・安全性を両立できます。
どうしても csproj を増やしたくないなら、EF 依存を呼び出し側から隠蔽する設計に切り替えるのが現実的な落としどころです。Package Source Mapping は統制には有効ですが、IDE の補完不整合そのものを消すものではない点に注意してください。
付録:サンプル csproj と最小コード
クラスライブラリ(Data/Data.csproj)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
</ItemGroup>
</Project>
コンソール(App/App.csproj)─ 明示参照ありの例
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Data\Data.csproj" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
</ItemGroup>
</Project>
Directory.Packages.props(任意)
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>
</Project>
最小コード(IDE エラーの再現と解消)
// App/Program.cs
using System;
using Microsoft.Extensions.DependencyInjection;
// ↓ これが IDE で未解決になりうる
using Microsoft.EntityFrameworkCore;
var services = new ServiceCollection()
.AddDbContext(opt => opt.UseMySql(
"Server=localhost;Database=demo;User=root;Password=pass;",
new MySqlServerVersion(new Version(8, 0, 36))))
.BuildServiceProvider();
Console.WriteLine("OK");
上記が IDE で解決できない場合は、App 側に EF Core の明示参照を追加すれば安定します。
チェックリスト(貼り付けて使える運用テンプレート)
- 設計:EF 依存はドメイン境界内に閉じ、UI/アプリ層からは抽象で呼ぶ。
- 参照:アプリが直接使う API は
<PackageReference>を明示。 - 集中管理:
Directory.Packages.propsを採用、バージョンは 1 箇所に。 - CI:
dotnet list package --vulnerable --include-transitiveを定期実行。 - 再現性:
RestorePackagesWithLockFile=true、--locked-modeを使う。 - IDE 整備:ソリューション ルートで開く、設計時ビルドが崩れたらクリーン&再起動。
まとめ
VS Code の C# Dev Kit で「推移的パッケージ参照が見えない」問題は、設計時ビルドと CLI ビルドの解決戦略の差が表に出たものです。実務では、スタートアップ側で使う API を明示参照することが最短・確実の解決策であり、IDE の安定性・セキュリティ・チームでの可視性という観点でも利点しかありません。どうしても csproj を増やしたくないときは、依存の隠蔽(抽象化)で IDE に EF を要求させない設計に切り替えるのが現実的です。いずれの道を選ぶにせよ、中央パッケージ管理と CI による健全性チェックを併用することで、長期的な保守コストとリスクを大幅に下げられます。

コメント