ASP.NET Core×EF Core×SQLiteの接続文字列と保存場所を完全解説|db.dbはどこに作られる?IIS・Docker・Windowsサービス別のベストプラクティス

ASP.NET Core で Entity Framework Core(EF Core)と SQLite を使うとき、「UseSqlite("Data Source=db.db") と書いたらファイルはどこに作られるのか?」は、開発・本番で動く場所が変わるたびに必ず議題になります。本記事では “作業ディレクトリ(ワーキングディレクトリ)” をキーに、IIS、Windows サービス、Docker、コンソール/Worker、Azure など代表的なホスティング形態での保存場所を具体例と実装パターン付きで徹底解説します。

目次

結論と最短回答

接続文字列が Data Source=db.db のように相対パス(ファイル名のみを含む)なら、SQLite ファイルは アプリの「作業ディレクトリ」 に作成されます。 開発時は多くの場合「プロジェクトのルート(.csproj が置かれたフォルダ)」、シングルファイル配布や一部のホスティングでは「実行ファイルが置かれたフォルダ」やサービスのカレントディレクトリになります。確実に場所を制御したい場合は、絶対パスを使うか、IHostEnvironment.ContentRootPath などで相対パスを自前で解決してください。

// 例:最短で「Data」サブフォルダに明示保存(推奨パターン)
builder.Services.AddDbContext<AppDbContext>(options =>
{
    var env = builder.Environment; // IHostEnvironment
    var dbPath = Path.Combine(env.ContentRootPath, "Data", "db.db");
    Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);
    options.UseSqlite($"Data Source={dbPath}");
});

「作業ディレクトリ」とは何か ― 3つの基準パスを整理

パスの混乱は「基準点」を混同することから始まります。ASP.NET Core では次の 3 つを押さえましょう。

  • Environment.CurrentDirectory:プロセスの作業ディレクトリ。相対パスの Data Source=相対パス はここから解決されます。
  • IHostEnvironment.ContentRootPath(旧 IWebHostEnvironment にも同名):アプリの「コンテンツルート」。appsettings.json などの標準ファイルや Razor コンパイルの基準。多くのテンプレートで CurrentDirectory と一致しますが、必ず一致するとは限りません。
  • AppContext.BaseDirectory:実行アセンブリの配置ディレクトリ。シングルファイル配布や Windows サービスで頼れることが多い基準点。

相対パスの接続文字列は、原則として Environment.CurrentDirectory から解決されます。ホスティングによりここが変わるため、「ローカルでは動くのに本番で見失う」現象が起こります。

代表的なホスティングごとの保存先の実際

次の表は、UseSqlite("Data Source=db.db") と書いたときに どこに作られがちか をまとめたものです。環境準備やテンプレートの違いでブレるので、後述の「実際の解決結果をログする」テクニックで必ず確認しましょう。

ホスティング形態作業ディレクトリの傾向生成される想定パス(例)備考/注意
Visual Studio/dotnet run(開発)プロジェクトのルートC:\MyProject\db.dbbuilder.Environment.ContentRootPath ≒ プロジェクトルート。
IIS(In-Process)アプリの配置フォルダC:\inetpub\wwwroot\MyApp\db.dbアプリプールの書き込み権限が必要。
IIS(Out-Of-Process / Kestrel背後)配置フォルダまたは System32 ではない設定C:\sites\MyApp\db.db近年のテンプレートでは System32 にはならないが、明示解決が安全。
Windows サービス(Worker/Web)サービスの実行フォルダまたは AppContext.BaseDirectoryC:\Services\MyApp\db.db起動方法により CurrentDirectory がズレることがある。
Docker(Linuxコンテナ)コンテナの WORKDIR/app/db.db永続化は VOLUME や bind mount を使う。
Azure App Service(Linux/Windows)アプリのホーム/サイトディレクトリD:\home\site\wwwroot\db.db 等書き込み可能領域を選ぶ。ローカル一時領域は揮発性に注意。

相対パス/絶対パスの指定とその効果

絶対パスを使う(もっとも明確)

options.UseSqlite(@"Data Source=C:\Data\db.db"); // Windows
options.UseSqlite("Data Source=/var/lib/myapp/db.db"); // Linux
  • 保存先を完全に固定できます。
  • デプロイ先のフォルダ権限を事前に設定しておく必要があります。

相対パスを使う(作業ディレクトリ基準)

options.UseSqlite("Data Source=Data/db.db"); // or Data\\db.db on Windows literal
  • ディレクトリは自動では作成されないので、Directory.CreateDirectory が必要です。
  • ホスティングにより基準(作業ディレクトリ)がずれます。

安全確実:パスを自分で生成してから渡す(推奨)

運用でのトラブルを根本から減らすには、「相対 → 絶対」を自分で解決してから接続文字列へ埋め込みます。以下は最小かつ堅牢なパターンです。

// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
{
    string root = builder.Environment.ContentRootPath; // プロジェクト/アプリの基準
    string dbDir = Path.Combine(root, "Data");
    Directory.CreateDirectory(dbDir); // 無ければ作る
    string dbPath = Path.Combine(dbDir, "db.db");


options.UseSqlite(new SqliteConnectionStringBuilder
{
    DataSource = dbPath,
    Mode = SqliteOpenMode.ReadWriteCreate, // 必要に応じて
    Cache = SqliteCacheMode.Shared // 複数接続時の安定化に寄与
}.ToString());


}); 

文字列連結ではなく Path.Combine を使う理由は、OS ごとの区切り文字の違いや、末尾スラッシュの有無を意識せずに済むからです。

appsettings.json 管理と「再解決」テクニック

接続文字列を構成ファイルで管理するのは定番です。ただし Data Source=Data/db.db のような相対パスをそのまま使うと本番で迷子になりやすいので、起動時に再解決しましょう。

{
  "ConnectionStrings": {
    "Default": "Data Source=Data/db.db"
  }
}
// Program.cs
using Microsoft.Data.Sqlite;

var cs = builder.Configuration.GetConnectionString("Default")!;
var b = new SqliteConnectionStringBuilder(cs);

// Data Source が相対なら ContentRootPath から絶対化
if (!Path.IsPathRooted(b.DataSource))
{
var rooted = Path.Combine(builder.Environment.ContentRootPath,
b.DataSource.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(rooted)!);
b.DataSource = rooted;
}

builder.Services.AddDbContext(opt => opt.UseSqlite(b.ToString())); 

このやり方なら、appsettings.Development.json と本番で同じ値を持ちながら、実行時に安全な場所へ収めることができます。

フォルダ権限と運用上の注意

Windows(IIS/サービス)

  • IIS の場合はアプリプールの実行ユーザー(既定では IIS AppPool\{AppPool名})に書き込み権限を付与。
  • サービスの場合はサービス実行ユーザー(ローカルサービス/仮想アカウントなど)に付与。
  • 権限はフォルダ単位で。ファイル単体にだけ権限を付けても、新規作成時に失敗します。

Linux/コンテナ

  • chown と chmod で実行ユーザーに rwx(少なくとも rw)を付与。
  • Docker は WORKDIR と VOLUME(または bind mount)で永続化を設計。コンテナ内部だけに置くとコンテナ破棄で消えます。

SQLite 特有の運用 Tips(WAL・ReadOnly・ネットワーク共有)

  • WAL モード(ジャーナル):同時アクセスが多いなら PRAGMA journal_mode=WAL; を初期化時に実行すると読み取り同時性が向上します。
  • ReadOnly:読み取り専用で開くなら Mode=ReadOnly を併用。誤更新の事故を防げます。
  • ネットワーク共有:SQLite はローカルディスク前提のロック機構です。SMB/NFS 越しの更新はロック競合やパフォーマンス低下の原因になるため避けます。
// 例:WAL を使う初期化(アプリ起動時に一度だけ)
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();
    db.Database.OpenConnection();
    db.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL;");
}

マイグレーションとファイル作成タイミング

EF Core では dotnet ef database update(または Database.Migrate())を実行した その時点の接続文字列 を基にファイルが作成されます。つまり、マイグレーション実行時の作業ディレクトリが最初の配置場所になります。実運用では、Database.Migrate() をアプリ起動時に行うか、CI/CD のデプロイスクリプトで 期待する作業ディレクトリ に切り替えてからコマンドを実行しましょう。

// 起動時に自動マイグレーションする例(開発〜小規模向け)
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();
    db.Database.Migrate();
}

相対パスの落とし穴と対策チェックリスト

  • ディレクトリ未作成:Directory.CreateDirectory を忘れない。
  • バックスラッシュとエスケープ:C# の文字列リテラルは "C:\\Data\\db.db" または逐語的文字列 @"C:\Data\db.db" を使う。
  • CurrentDirectory の変動:サービス/IIS/ジョブスケジューラでは変わり得る。自前解決で固定化。
  • 相対パスの二重解決:appsettings.json に相対パスを書いたら、必ず起動時に自分で絶対化してから渡す。
  • |DataDirectory| トークン:ADO.NET の一部プロバイダだけの慣習です。Microsoft.Data.Sqlite を使うなら安易に頼らず、上記の自前解決を採用。

実際の解決結果(どこに作られたか)をログで可視化する

「どこに作られたか」を確信する最短の方法は、実行時に最終的な接続文字列をログに出すことです。

// DbContext.OnConfiguring などで
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
        optionsBuilder.UseLoggerFactory(loggerFactory);
    }
}

// 実際の Data Source を取り出して確認
var conn = context.Database.GetDbConnection();
var builder = new SqliteConnectionStringBuilder(conn.ConnectionString);
logger.LogInformation("SQLite DataSource => {Path}", builder.DataSource); 

具体的なコードパターン集

パターンA:ContentRoot 配下の「Data」フォルダに固める

var root = builder.Environment.ContentRootPath;
var dbPath = Path.Combine(root, "Data", "db.db");
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);

builder.Services.AddDbContext(o => o.UseSqlite($"Data Source={dbPath}")); 

パターンB:環境変数で上書き可能にする(12-factor)

// appsettings.json : "ConnectionStrings:Default": "Data Source=Data/db.db"
var raw = builder.Configuration.GetConnectionString("Default")!;
var sb = new SqliteConnectionStringBuilder(raw);

var envPath = Environment.GetEnvironmentVariable("APP_DB_PATH");
if (!string.IsNullOrWhiteSpace(envPath)) sb.DataSource = envPath;

if (!Path.IsPathRooted(sb.DataSource))
sb.DataSource = Path.Combine(builder.Environment.ContentRootPath, sb.DataSource);

Directory.CreateDirectory(Path.GetDirectoryName(sb.DataSource)!);

builder.Services.AddDbContext(o => o.UseSqlite(sb.ToString())); 

パターンC:Docker でボリューム永続化

# Dockerfile(抜粋)
WORKDIR /app
# 最終イメージに app を配置済みとして…
VOLUME ["/data"]  # 永続データ用
// コンテナ内では /data 配下に作る
var dbPath = Path.Combine("/data", "db.db");
Directory.CreateDirectory("/data");
builder.Services.AddDbContext&lt;AppDbContext&gt;(o =&gt; o.UseSqlite($"Data Source={dbPath}"));
# 実行時にホストへ永続化
docker run -d -p 8080:8080 -v C:\MyAppData:/data myimage:latest

パターンD:Windows サービスで AppContext.BaseDirectory 起点

var baseDir = AppContext.BaseDirectory;
var dbPath  = Path.Combine(baseDir, "db.db");
builder.Services.AddDbContext&lt;AppDbContext&gt;(o =&gt; o.UseSqlite($"Data Source={dbPath}"));

トラブルシューティング(エラー別の原因と対策)

症状主な原因対策
SQL logic error: unable to open database fileフォルダが無い/権限が無い/相対パスが誤基準CreateDirectory を追加/権限付与/絶対化して固定
attempt to write a readonly databaseファイル/フォルダが読み取り専用、または Mode=ReadOnlyACL/パーミッションを修正/接続文字列のモードを見直し
database is locked長時間の書き込みトランザクション/同時更新トランザクション粒度を小さく/WAL 有効化/リトライポリシー
開発では動くが本番で「見つからない」作業ディレクトリが異なるContentRootPath 起点に自前で絶対化して渡す

ベストプラクティス要点

  • 保存場所はコードで決める:相対のまま EF に委ねない。Path.Combine + Directory.CreateDirectory をセットで。
  • 構成は 1 箇所:appsettings.json の相対値は起動時に絶対化し、アプリ全体で同じインスタンスを使う。
  • 書き込み権限を先に設計:IIS/サービス/コンテナで「誰が書くのか」を決め、デプロイ手順に含める。
  • 永続化戦略:コンテナはボリューム前提。PaaS は書き込み可能なホーム領域を使用。
  • 運用ログ:起動時に DataSource を INFO ログ出力しておく。

よくある質問(FAQ)

Q. UseSqlite("Data Source=db.db") で作られる場所は常に「プロジェクトのルート」ですか?

A. いいえ。作業ディレクトリに作られます。開発では多くの場合プロジェクトルートに一致しますが、IIS、サービス、コンテナ、クラウドでは異なることがあります。

Q. |DataDirectory| トークンは使えますか?

A. 一部の ADO.NET プロバイダの文化です。EF Core の標準的な Microsoft.Data.Sqlite を使う前提では、自前で絶対化する設計を強く推奨します。

Q. EnsureCreated() と Migrate() はどちらを使えば?

A. スキーマ進化を伴う運用では Migrate() を推奨。EnsureCreated() は最初期のプロトタイプ向けです。

Q. ファイルをアプリと同じ場所に置くのは安全ですか?

A. 小規模・単体インスタンスなら実務上問題ないケースが多いですが、バックアップやローテーション、権限境界を考えると C:\Data のように データ専用のフォルダ を切り出す方が管理しやすくなります。

Q. 本番でパスを切り替えたい

A. 環境変数や KeyVault で接続文字列を上書きし、それが相対なら起動時に絶対化するフローを組み込みます(前掲のパターンB)。

サンプル:最小の ASP.NET Core + EF Core + SQLite

// Program.cs(.NET 8 以降の最小ホスト)
var builder = WebApplication.CreateBuilder(args);

// DB パスを明示
var dbPath = Path.Combine(builder.Environment.ContentRootPath, "Data", "db.db");
Directory.CreateDirectory(Path.GetDirectoryName(dbPath)!);

builder.Services.AddSqlite($"Data Source={dbPath}");
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

// 最初にマイグレーション
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
db.Database.Migrate();
}

app.MapGet("/", (AppDbContext db) => db.Items.ToList());
app.Run();

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

public class TodoItem
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
} 

まとめ

  • 相対パスは作業ディレクトリ基準で解決される。環境により変わる。
  • 絶対パス or 自前絶対化で保存場所を統一する。
  • フォルダ作成と権限付与をデプロイ手順に含める。
  • Docker/クラウドでは永続化ストレージを設計から組み込む。
  • ログで DataSource を常に確認。問題の切り分けが速くなる。

この原則を守れば、「どこに作られた?」で悩む時間はゼロになります。SQLite の強みである「単一ファイル」を味方に、配布・バックアップ・移行をシンプルに保ちましょう。

この記事を書いた人

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

コメント

コメントする

目次