SQLiteはBLOBカラムで任意のバイナリデータを保存できるため、 画像・PDF・その他ファイルをDBにまとめて管理したい、というニーズは多いです。 一方で、サイズ・パフォーマンス・バックアップ・セキュリティを考えずに突っ込むと、 「DBが巨大化して壊れやすくなる」リスクもあります。 この記事では、SQLiteにバイナリを保存する現実的な設計とC#実装パターンを整理します。
・SQLiteのBLOBカラム設計
・画像・PDF・任意ファイルの保存/取得コード(C#)
・DB内保存 vs 外部ファイル管理のメリット・デメリット
・ファイルサイズの目安とパフォーマンス
・バックアップ・セキュリティの考え方
・業務アプリ向けベストプラクティス
1. SQLiteでバイナリを扱う基本:BLOBカラム
SQLiteでは、バイナリデータはBLOB型で保存します。 画像専用型などはなく、すべて「バイト列」として扱います。
■ 典型的なテーブル設計例
CREATE TABLE Files (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
FileName TEXT NOT NULL,
ContentType TEXT NOT NULL, -- image/png, application/pdf など
Data BLOB NOT NULL,
CreatedAt TEXT NOT NULL
);
最低限、ファイル名・ContentType・バイナリ本体を持たせておくと扱いやすくなります。
2. C#での保存:ファイル → BLOB
C#(Microsoft.Data.Sqlite)からファイルを読み込み、 そのままBLOBとしてINSERTする基本パターンです。
■ ファイルをDBに保存するコード例
using Microsoft.Data.Sqlite;
public void SaveFileToDb(string filePath)
{
var cs = "Data Source=files.db";
var fileName = Path.GetFileName(filePath);
var contentType = GetContentType(filePath); // 拡張子から判定するなど
var bytes = File.ReadAllBytes(filePath);
using var con = new SqliteConnection(cs);
con.Open();
var sql = @"
INSERT INTO Files (FileName, ContentType, Data, CreatedAt)
VALUES (@name, @type, @data, @createdAt);
";
using var cmd = new SqliteCommand(sql, con);
cmd.Parameters.AddWithValue("@name", fileName);
cmd.Parameters.AddWithValue("@type", contentType);
cmd.Parameters.AddWithValue("@data", bytes);
cmd.Parameters.AddWithValue("@createdAt", DateTime.UtcNow.ToString("o"));
cmd.ExecuteNonQuery();
}
private string GetContentType(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
return ext switch
{
".png" => "image/png",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".pdf" => "application/pdf",
_ => "application/octet-stream"
};
}
小さめのファイルであれば File.ReadAllBytes で問題ありませんが、
大きなファイルはストリームで少しずつ読み書きする方が安全です。
3. C#での取得:BLOB → ファイル/画像
■ DBからファイルを取り出して保存する例
public void ExportFileFromDb(int id, string outputPath)
{
var cs = "Data Source=files.db";
using var con = new SqliteConnection(cs);
con.Open();
var sql = "SELECT FileName, ContentType, Data FROM Files WHERE Id = @id";
using var cmd = new SqliteCommand(sql, con);
cmd.Parameters.AddWithValue("@id", id);
using var reader = cmd.ExecuteReader();
if (!reader.Read()) return;
var fileName = reader.GetString(0);
var contentType = reader.GetString(1);
var bytes = (byte[])reader["Data"];
var path = Path.Combine(outputPath, fileName);
File.WriteAllBytes(path, bytes);
}
WPFなどで画像として表示したい場合は、
byte[] から BitmapImage に変換してバインドします。
■ byte[] → BitmapImage(WPF)
public static BitmapImage ToBitmapImage(byte[] bytes)
{
using var ms = new MemoryStream(bytes);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
4. DB内保存 vs 外部ファイル管理:どっちが正解?
バイナリを扱うとき、必ず出てくるのが 「DBに入れるか?ファイルとして置くか?」問題です。
■ DB内保存(BLOB)のメリット
- バックアップが「DBファイルだけ」で完結する
- ファイルとメタ情報の整合性が取りやすい
- 権限管理をDB側で一元化しやすい
■ DB内保存のデメリット
- DBファイルが巨大化しやすい
- VACUUMに時間がかかる
- 壊れたときの影響範囲が大きい
■ 外部ファイル管理のメリット
- DBは軽く保てる
- ファイルサーバーやクラウドストレージと相性が良い
- CDNなどで配信しやすい
■ 外部ファイル管理のデメリット
- DBとファイルの整合性が崩れやすい(パスだけ残るなど)
- バックアップ対象が増える
- 権限管理が二重になる(DB+ファイルシステム)
・小規模/単体アプリ:DB内保存(BLOB)でもOK
・中〜大規模/長期運用:外部ファイル管理+DBはメタ情報のみ (または「サムネイルだけDB、原本はファイル」などのハイブリッド)
5. ファイルサイズとパフォーマンスの目安
SQLite自体は数GBクラスのDBも扱えますが、 1ファイルDBであることを忘れてはいけません。
■ 目安として意識したいライン
- 1ファイルあたり数MB程度:BLOBでもほぼ問題なし
- 数十MB〜数百MB:用途をよく検討(頻繁に更新しないならまだ許容)
- 動画・巨大PDFなど:基本は外部ファイル管理を検討
また、BLOBを多用するとVACUUMやバックアップ時間が伸びるため、 運用時のメンテナンス時間も考慮する必要があります。
6. トランザクションと一括保存
複数ファイルをまとめて保存する場合は、 トランザクションで一括処理するのが定番パターンです。
using var con = new SqliteConnection(cs);
con.Open();
using var tran = con.BeginTransaction();
try
{
foreach (var filePath in filePaths)
{
var fileName = Path.GetFileName(filePath);
var contentType = GetContentType(filePath);
var bytes = File.ReadAllBytes(filePath);
var sql = @"
INSERT INTO Files (FileName, ContentType, Data, CreatedAt)
VALUES (@name, @type, @data, @createdAt);
";
using var cmd = new SqliteCommand(sql, con, tran);
cmd.Parameters.AddWithValue("@name", fileName);
cmd.Parameters.AddWithValue("@type", contentType);
cmd.Parameters.AddWithValue("@data", bytes);
cmd.Parameters.AddWithValue("@createdAt", DateTime.UtcNow.ToString("o"));
cmd.ExecuteNonQuery();
}
tran.Commit();
}
catch
{
tran.Rollback();
throw;
}
これにより、途中でエラーが出ても中途半端な状態を防げると同時に、 パフォーマンスも大幅に向上します。
7. セキュリティとバックアップの観点
バイナリに個人情報・機密情報が含まれる場合、 セキュリティとバックアップの設計が重要になります。
■ セキュリティのポイント
- DBファイルの配置場所とOS権限を制限する
- 必要に応じて暗号化SQLite or カラム暗号化を検討
- バックアップファイルの持ち出しに注意(USB・クラウドなど)
■ バックアップのポイント
- DB内保存の場合:DBファイルのコピーだけで完結するが、サイズに注意
- 外部ファイル管理の場合:DB+ファイルストレージ両方のバックアップが必要
- オンラインバックアップAPI(BackupDatabase)を使うと安全
8. 業務アプリ向けベストプラクティス
- バイナリはBLOBカラムで扱う(画像・PDF・任意ファイル)
- 小規模ならDB内保存、大規模なら外部ファイル管理を検討
- ファイル名・ContentType・作成日時などのメタ情報を必ず持つ
- 複数ファイル保存はトランザクションで一括処理
- WPFなどでは byte[] → BitmapImage 変換でバインド
- DBサイズ・VACUUM・バックアップ時間も運用設計に含める
- 機微情報を含む場合は暗号化・権限・バックアップをセットで考える
まとめ:SQLiteのバイナリ保存は“規模と運用”で決める
- SQLiteはBLOBで画像・PDF・任意ファイルを問題なく扱える
- ただし、DB内保存はサイズ・パフォーマンス・バックアップに影響する
- 小規模ならシンプルにDB内、大規模なら外部ファイル or ハイブリッドが現実的
「全部DBに入れれば楽」と 「全部ファイルにすれば軽い」の間に、 アプリの規模・運用・セキュリティに合わせた最適なポイントがあります。 この記事のパターンをベースに、 自分のプロジェクトにとってちょうどいいバイナリ保存戦略を設計してみてください。