.NET 9 Minimal API×EF CoreでSQLiteを安全に共有する方法|No such table対処とマイグレーション運用の完全ガイド

別のPCでクローンしたら No such table: Dishes──ローカルSQLiteの“ファイル=データベース”という仕様を忘れると、開発体験は一気に険しくなります。本稿は .NET 9 の Minimal API と Entity Framework Core を前提に、ノートPCとデスクトップ間でSQLiteを確実に共有・再現するための実践解を、エラー原因の切り分けから再発防止まで網羅的に解説します。

目次

症状の整理:No such table: Dishes が出る理由

エラーの本質は「アプリが参照している .db ファイルに Dishes テーブルが存在しない」ことです。SQLiteは“1ファイル=1データベース”のため、そのファイル自体が無ければ当然テーブルもありません。Gitでソースだけを共有し、DBファイルを共有していない場合に高確率で発生します。

  • ノートPCで生成された Data/Dishes.db が、デスクトップ側のクローンに存在しない。
  • 存在していても、異なるパスを見に行っている(相対パス/実行フォルダー差異)。
  • EF Coreのマイグレーションが未適用(スキーマが作られていない)。

なお、.vs/slnx.sqlite はVisual Studioのソリューション設定用データベースであり、アプリの実データベースではありません。ここにテーブルを作ろうとしても意味はありません。

解決の全体像:4つの方策とトレードオフ

まずは対応方針を俯瞰しましょう。基本的には「ソースからDBを再構築する(A/C)」か「DBファイルごと共有する(B)」、あるいは「そもそもサーバーDBに移行する(D)」のいずれかです。

方策手順・ポイント長所注意点
A. 各マシンでDBを自動生成(推奨)1. Migrations フォルダーをリポジトリに含める
2. クローン後に dotnet ef database update を実行 または 起動時に Database.Migrate() を呼び出す
Gitにバイナリを置かず軽量/常に最新スキーマを再現初期データが必要ならSeed処理をコードに追加
B. .db ファイルをリポジトリに含める1. .gitignore から当該 .db を除外
2. git add Data/Dishes.db → git commit/push
クローンだけで即動作差分管理に不向き/情報漏えい・肥大化リスク/頻繁更新はGit LFS推奨
C. 起動時にSeedデータを挿入Database.Migrate() 後に if (!db.Dishes.Any()) { ... } でテストデータ投入どの環境でも同じ初期データ本番と混同しない環境判定が必須
D. 共有アクセス要件ならサーバーDBへSQLiteは同時書き込みに弱い。Web API + サーバーRDBMS(Azure SQL / PostgreSQL 等)へ移行同時編集・スケール容易環境構築コスト増

まずはここから:接続文字列と基準パスの落とし穴

「DBが無い」だけでなく「パスを見失っている」ケースもよくあります。相対パスは実行コンテキストに依存するため、Visual Studio実行と dotnet run、テスト実行で基準フォルダーが変わり得ます。

// appsettings.json(一例)
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=Data/Dishes.db"
  }
}

この指定は 実行フォルダー(例:bin/Debug/net9.0)を基準に解決される場合があり、環境により Data/Dishes.db が見つからないことがあります。もっと安全にするには ContentRoot へ明示的に解決しましょう。

// Program.cs(.NET 9 Minimal API)
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
{
    var contentRoot = builder.Environment.ContentRootPath;
    var dbPath = Path.Combine(contentRoot, "Data", "Dishes.db");
    Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
    options.UseSqlite($"Data Source={dbPath}");
});

var app = builder.Build();

// 起動時にマイグレーション&Seed
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.Migrate();
    SeedData.Run(db, app.Logger, app.Environment);
}

app.MapGet("/dishes", async (AppDbContext db) => await db.Dishes.OrderBy(x => x.Id).ToListAsync());
app.Run();
実行方法基準パス典型的なDB実体の場所対策
Visual Studio デバッグ実行ContentRootPath(プロジェクトルート)<プロジェクト>/Data/Dishes.dbContentRootPathからの絶対化を徹底
dotnet runカレントディレクトリ同上(ただし cd 先に依存)ContentRoot明示/絶対パス生成
xUnit などテストテストホストの作法次第見失いがちテスト用にインメモリや一時ファイルを利用

方策A:マイグレーションで各マシンにDBを自動生成(推奨)

ソースからスキーマを再現する王道パターンです。チーム開発にも最適。

必要パッケージ

dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
# or ローカルツール
dotnet new tool-manifest
dotnet tool install dotnet-ef
dotnet tool restore

モデルとDbContextの最小実装

public class Dish
{
    public int Id { get; set; }
    public string Name { get; set; } = default!;
    public int Price { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options) : base(options) {}
public DbSet Dishes => Set();


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Dish>()
        .Property(x => x.Name)
        .HasMaxLength(120)
        .IsRequired();
    modelBuilder.Entity<Dish>()
        .HasIndex(x => x.Name);
}


} 

マイグレーション作成と適用

# 初回
dotnet ef migrations add InitialCreate
# スキーマ変更時
dotnet ef migrations add AddDishPriceIndex
# 適用(ローカルDBを作成/更新)
dotnet ef database update

Migrations フォルダーは必ずコミットします。これにより、クローン直後のPCでも dotnet ef database update だけで同じスキーマを再現可能です。

アプリ起動時に自動適用する(安全網)

前述の db.Database.Migrate() を起動時に呼び出しておけば、開発中はコマンドを忘れても自動で最新化できます。CIや本番での適用は、ロールバック戦略や権限設計の都合で別運用にすることもあります。

Seedデータ(テスト・デモ用)

public static class SeedData
{
    public static void Run(AppDbContext db, ILogger logger, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment()) return; // 本番混入を防ぐ


    if (!db.Dishes.Any())
    {
        db.Dishes.AddRange(
            new Dish { Name = "チキンカレー", Price = 880 },
            new Dish { Name = "ペペロンチーノ", Price = 900 },
            new Dish { Name = "味噌ラーメン", Price = 850 }
        );
        db.SaveChanges();
        logger.LogInformation("Seeded initial dishes.");
    }
}


} 

EnsureCreated() と Migrate() の違い

  • EnsureCreated() はマイグレーション履歴を使わず、空のDBに対して一気にスキーマを作ります。既存DBの進化には不向き。
  • Migrate() はマイグレーション履歴を用いて段階的に更新します。チーム開発や長期運用ではこちらが必須。

方策B:.db ファイルをリポジトリに含める(手軽だが慎重に)

デモや小規模ツールなら「クローンしたらすぐ動く」利点は大きいですが、以下を必ず検討してください。

  1. 機微情報(個人情報・秘密データ)を含めない。
  2. ファイル肥大化・コンフリクト対策にGit LFSを使う(頻繁更新時)。
  3. SQLiteのWAL/journalファイル(.db-wal, .db-shm 等)を正しく扱う。基本はクローズ後に .db 本体だけをコミット。
# 例:LFSで追跡
git lfs install
git lfs track "Data/*.db"
git add .gitattributes Data/Dishes.db
git commit -m "Add seeded SQLite DB"
git push

将来スキーマを変更するなら、結局はマイグレーションを運用することになります。学習コストを避けず、方策Aへ寄せるのが長期的には安全です。

方策C:起動時にSeedデータを挿入する(再現性の担保)

テーブルは空でも動くが、画面やAPIの動作確認に最低限のサンプルが欲しい──そんな時にSeedが効きます。環境判定(Developmentのみ等)を必ず入れ、本番DBにテストデータが混入しないよう徹底しましょう。

Seedの目的実装ポイントアンチパターン
UI/APIの即時確認起動時に空チェック→追加毎回Insertして重複作成
ドメインのサンプル共有環境変数やビルド構成でON/OFF本番でもSeedを常時実行
テスト再現性テストプロジェクト側で独自に準備本番DBをテストに使い回す

方策D:同時編集したいならサーバーRDBMSへ移行

SQLiteは軽量・簡単が魅力ですが、複数クライアントからの同時書き込みや遠隔共有に向きません。Web API配下で複数PCからアクセスするなら、SQL Server / PostgreSQL / MySQL などのサーバーRDBMSを選び、接続文字列はユーザーシークレットや環境変数で管理しましょう。

// 例:PostgreSQLへ切り替え
builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt;
    options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
# ローカル秘密情報に格納(ユーザーシークレット)
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Host=localhost;Database=appdb;Username=app;Password=secret"

クラウドストレージ同期は最終手段

OneDrive/Dropbox/Google Drive等で .db を共有すると、ロック競合やWALファイルとの不整合で破損リスクが上がります。読み取り専用・単独編集のドキュメント閲覧用途ならまだしも、開発中のDBの同時書き込み共有には使わないでください。

Visual Studioの .vs/slnx.sqlite について

繰り返しになりますが、.vs/slnx.sqlite はソリューションのIDE内部データ用のSQLiteです。ここにアプリのテーブルを作ることはできても意味がありません。Git管理の対象外にして構いません。

実践レシピ:クローン直後に確実に動かす手順(チェックリスト)

  1. リポジトリをクローン(git clone ...)。
  2. ソリューション直下に Data フォルダーが存在するか確認。無ければ作成。
  3. 方策Aの場合:dotnet tool restore → dotnet ef database update。
    または起動時 Migrate() が走る実装にしておく。
  4. 方策Bの場合:Data/Dishes.db が存在し、アプリの接続先と一致しているか確認。
  5. アプリを起動。/dishes などのエンドポイントで取得できるか確認。
  6. まだエラーなら次の「トラブルシュート」を実施。

トラブルシュート:それでも No such table が消えないとき

  • 接続先が違う:ログに出るDBパス(Data Source=...)を必ず記録。想定と一致しなければ、ContentRoot を基準に絶対パスを組み立てる。
  • マイグレーション履歴が空:dotnet ef migrations list で確認。空ならモデルから作成(dotnet ef migrations add InitialCreate)。
  • 実DBにテーブルが無い:SQLiteツールで SELECT name FROM sqlite_master WHERE type='table';。Dishes が無ければ database update を適用。
  • 複数DbContext:接続文字列やマイグレーション履歴のスコープがコンテキストごとにズレていないか確認。
  • 構成ファイルの切り替え:appsettings.Development.json と appsettings.json のどちらが読まれているか。環境変数 ASPNETCORE_ENVIRONMENT をチェック。

セキュリティと情報管理:DBをGitに入れる前に

SQLiteファイルには全データが平文で入っています。公開リポジトリにコミットするのは怪我のもと。社内でもアクセス制御・暗号化・秘匿情報分離を検討しましょう。機微データを含むなら、そもそも方策Bは選ばず、方策A+Seedや方策Dへ。

開発効率を上げる周辺Tips

CIでマイグレーションを検証

# GitHub Actions 例(抜粋)
dotnet restore
dotnet build --no-restore
dotnet tool restore
dotnet ef database update --project src/App --startup-project src/App
dotnet test

インメモリ/一時ファイルでのテスト

// インメモリSQLite(注意:複数接続やライフタイムに癖あり)
var keepAlive = new SqliteConnection("DataSource=:memory:");
keepAlive.Open();
options.UseSqlite(keepAlive);

絶対パス生成のユーティリティ化

public static class DbPath
{
    public static string FromContentRoot(IHostEnvironment env, params string[] segments)
    {
        var path = segments.Prepend(env.ContentRootPath).ToArray();
        var full = Path.Combine(path);
        Directory.CreateDirectory(Path.GetDirectoryName(full)!);
        return full;
    }
}

よくある質問(FAQ)

Q:.dbをコピーしたのにエラーは消えない?
A:アプリが別の場所を見ています。起動ログの接続文字列で実際のパスを確認し、ContentRoot 経由の絶対パスに統一しましょう。

Q:EnsureCreated() ではダメ?
A:短命なPoCならアリですが、スキーマが進化すると破綻します。チーム/長期運用は Migrate() 一択です。

Q:OneDriveで同期して共同編集できる?
A:書き込み競合で破損のリスクが高いです。読み取り専用での共有に限定するか、方策Dを検討してください。

Q:.db-wal や .db-shm ファイルはコミットすべき?
A:基本は不要です。アプリを完全に終了させ、.db 本体のみコミットしてください。

Q:マイグレーションを本番で自動適用してよい?
A:開発はOKでも本番は要審査です。停止時間・バックアップ・ロールバックの設計とセットで運用してください。

サンプル一式:最小構成で“再現できる”プロジェクト

以下のファイル群を持つだけで、どのPCでも同じスキーマ+サンプルデータを自動生成できます。

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext(opt =>
{
var dbPath = Path.Combine(builder.Environment.ContentRootPath, "Data", "Dishes.db");
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
opt.UseSqlite($"Data Source={dbPath}");
});

var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
db.Database.Migrate();
SeedData.Run(db, app.Logger, app.Environment);
}

app.MapGet("/dishes", async (AppDbContext db) => await db.Dishes.ToListAsync());
app.MapPost("/dishes", async (AppDbContext db, Dish input) => {
db.Add(input);
await db.SaveChangesAsync();
return Results.Created($"/dishes/{input.Id}", input);
});

app.Run(); 

AppDbContext.cs / Dish.cs

public class Dish
{
    public int Id { get; set; }
    public string Name { get; set; } = default!;
    public int Price { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options) : base(options) {}
public DbSet Dishes => Set();
} 

SeedData.cs

public static class SeedData
{
    public static void Run(AppDbContext db, ILogger logger, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment()) return;
        if (db.Dishes.Any()) return;


    db.Dishes.AddRange(
        new Dish { Name = "ハンバーグ", Price = 980 },
        new Dish { Name = "カルボナーラ", Price = 1100 },
        new Dish { Name = "牛丼", Price = 650 }
    );
    db.SaveChanges();
    logger.LogInformation("Seed completed.");
}


} 

マイグレーションの作成と共有

dotnet ef migrations add InitialCreate
git add Migrations
git commit -m "Add InitialCreate migration"
git push

発展:運用で困らないための設計指針

  • 同じコードで同じ結果:マイグレーションとSeedで「再現可能性」を保証。
  • パスは絶対化:ContentRoot 基準に統一。相対パスをやめる。
  • 秘密はコードに置かない:接続文字列はユーザーシークレット/環境変数。
  • DBをコミットするなら理由を書け:READMEに意図・運用・注意点を明記。
  • 将来を見据える:同時編集・スケールが必要なら早めにサーバーRDBMSへ。

まとめ

No such table: Dishes の本当の原因は「ファイルがない/スキーマがない/パスが違う」のいずれかです。SQLiteは“ファイル=データベース”であることを踏まえ、方策A:マイグレーションで自動再生成+Seed を基本戦略に据えれば、ノートPC⇔デスクトップ間の移動でもエラーなく同じ状態を即時再現できます。データ共有・同時編集まで踏み込むなら、早期にサーバーRDBMSへ移行する判断が、将来の保守コストを大きく下げます。

この記事を書いた人

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

コメント

コメントする

目次