DataGridView:SQLite のデータをバインドして表示・編集保存する方法(サンプルあり)

📋 目次(クリックで展開)

Windows Forms でローカル環境にデータを保存するデスクトップアプリやスタンドアロンの業務システムを開発する際、軽量なデータベースである SQLite と DataGridView を組み合わせる機会があると思います。

データベースとの連携では、「接続やSQLの処理が難しそう」、「画面の変更をデータベースに書き戻す処理が面倒そう」と感じるかもしれません。しかし、旧来の DataAdapterDataTable の仕組みにを活用することで、少ないコード量でデータの取得から一括保存までを実装することができます。

今回は、SQLite のデータを DataGridView にバインド表示し、画面上での追加・変更・削除をボタン一つでデータベースに反映する手順を解説します。

事前準備:NuGet パッケージのインストール

C# から SQLite を扱うために、Visual Studio の NuGet パッケージマネージャーを使って環境を構築します。最新の .NET 環境で SQLite を利用するには、以下の2つのパッケージをインストールする必要があります。

  1. System.Data.SQLite:データの仲介役となる SQLiteDataAdapter や、保存用の SQL を自動生成してくれる SQLiteCommandBuilder が含まれているベースパッケージです。NuGet での検索結果のトップに表示される「無印」のものを選択してください。
  2. SQLitePCLRaw.bundle_e_sqlite3:バージョン 2.0.x 以上の System.Data.SQLite では、データベースを動かすためのネイティブエンジン本体(e_sqlite3.dll)がパッケージから除外される仕様変更がありました。これを入れ忘れると、実行時に System.DllNotFoundException(Unable to load DLL 'e_sqlite3') というエラーでアプリが強制終了してしまいます。このエラーを防ぐために、必ずこのパッケージもセットで検索して追加インストールしてください。

インストール手順

  1. Visual Studio のメニューから「ツール」 ➔ 「NuGet パッケージ マネージャー」 ➔ 「ソリューションの NuGet パッケージの管理」を開きます。
  2. 「参照」タブで System.Data.SQLite を検索してプロジェクトにインストールします。
  3. 続けて「参照」タブで SQLitePCLRaw.bundle_e_sqlite3 を検索し、こちらも同様にプロジェクトにインストールします。

これで、最新の環境でも安全に SQLite を動かす準備が整いました。

実装コード

下記は、フォームの起動時に SQLite データベース(ファイル)を自動生成して初期データを表示し、画面上で編集した内容を「保存」ボタンで一括してデータベースに書き戻すサンプルコードです。画面(Form1)に dataGridView1 と、保存用のボタン btnSave を配置しています。

Form1.cs
public partial class Form1 : Form
{
private const string ConnectionString = "Data Source=sample.db;Version=3;";
private SQLiteDataAdapter? _adapter;
private DataTable _dataTable = new DataTable();
public Form1()
{
InitializeComponent();
PrepareDatabase();
InitializeDataGridView();
}
private void PrepareDatabase()
{
using var conn = new SQLiteConnection(ConnectionString);
conn.Open();
string createTableSql = @"
CREATE TABLE IF NOT EXISTS Product (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Code TEXT NOT NULL,
Name TEXT NOT NULL,
Price INTEGER NOT NULL
);";
using var cmd = new SQLiteCommand(createTableSql, conn);
cmd.ExecuteNonQuery();
using var checkCmd = new SQLiteCommand("SELECT COUNT(*) FROM Product", conn);
long count = (long)(checkCmd.ExecuteScalar() ?? 0);
if (count == 0)
{
using var insertCmd = new SQLiteCommand(
"INSERT INTO Product (Code, Name, Price) VALUES ('P001', 'ノートパソコン', 120000), ('P002', 'USBメモリ', 2500)", conn);
insertCmd.ExecuteNonQuery();
}
}
private void InitializeDataGridView()
{
var conn = new SQLiteConnection(ConnectionString);
string selectSql = "SELECT Id, Code, Name, Price FROM Product";
_adapter = new SQLiteDataAdapter(selectSql, conn);
_adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
var builder = new SQLiteCommandBuilder(_adapter);
// 1. データをDataTableに読み込む
_adapter.Fill(_dataTable);
// 2. 既存データからIdの「最大値」を割り出す
int maxId = 0;
foreach (DataRow row in _dataTable.Rows)
{
if (row.RowState != DataRowState.Deleted)
{
int currentId = Convert.ToInt32(row["Id"]);
if (currentId > maxId)
{
maxId = currentId;
}
}
}
// 3. 一度自動採番を切り、算出した「最大値 + 1」からスタートするように強制リセット
var idColumn = _dataTable.Columns["Id"];
if (idColumn != null)
{
idColumn.AutoIncrement = false; // 内部カウンターを一度破壊
idColumn.AutoIncrementSeed = maxId + 1; // 既存の最大値 + 1 を次の番号に指定(例:3)
idColumn.AutoIncrementStep = 1; // 1ずつ正の方向に増やしていく
idColumn.AutoIncrement = true; // 採番機能を再有効化
}
// 4. DataGridViewにバインド
dataGridView1.DataSource = _dataTable;
// 5. Id 列を非表示にする
if (dataGridView1.Columns["Id"] != null)
{
dataGridView1.Columns["Id"]?.Visible = false;
}
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
private void btnSave_Click(object sender, EventArgs e)
{
if (_adapter == null) return;
try
{
_adapter.Update(_dataTable);
MessageBox.Show("変更内容をデータベースに保存しました。", "確認", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"保存中にエラーが発生しました:\n{ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}

サンプルアプリケーション

DataGridView_Bind_SQLite.zip
※ サンプルアプリケーションは、.NET 10 をターゲットに作成しています。

解説:今回のコードで登録・更新・削除ができる背景

通常、データベースの更新処理を自前で書こうとすると、画面上の行をループで走査し、「新しく追加された行なら INSERT 文」、「変更された行なら UPDATE 文」、「削除された行なら DELETE 文」をそれぞれの行に対して発行する、泥臭いコードが必要になります。

これを裏側で肩代わりしてくれているのが、SQLiteDataAdapterSQLiteCommandBuilder の組み合わせです。

  1. DataGridView 上で行を足したり消したりすると、バインドされている DataTable 内部の各行に「追加」、「変更」、「削除」という状態フラグ(RowState)が自動で記録されます。
  2. 保存ボタンが押されて _adapter.Update(_dataTable) が実行されると、DataAdapter は各行のフラグを読み取ります。
  3. SQLiteCommandBuilder が、そのフラグに合わせた適切な SQL文を裏側で自動的に組み立てて SQLite に対して発行します。

この仕組みにより、開発者は面倒な SQL の個別発行ロジックを書くことなく、数行のコードでデータ同期画面を構築することができます。

注意点:新規行追加時のエラーとソート崩れを防ぐ「最大値+1」の付与

SQLite側で Id を自動採番(AUTOINCREMENT)に設定して _adapter.Fill() を実行すると、生成された DataTableId 列は自動的に「重複を絶対に許さない主キー列」と認識されます。

この状態で、単純に Id 列を画面から隠したり ReadOnly にしただけで新規行を追加しようとすると、.NET の内部カウンターの仕様により、一律で「1」を割り振ろうとする不具合が起き、既存データとの衝突エラー(ConstraintException)が発生してしまいます。

よくある「一時的にマイナスの仮ID(-1, -2…)を振る」では、画面上で列の並び替え(ソート)を行った際に、未保存のデータがリストの上下にバラバラに散らばってしまうというUI上の重大なデメリットが発生します。

これを防ぐための工夫として、今回のサンプルコードのステップ2・3にある「最大値+1の採番」です。

// 既存の最大値(maxId)を算出した後、カウンターを上書き設定する
idColumn.AutoIncrement = false;
idColumn.AutoIncrementSeed = maxId + 1; // 最大値 + 1 からスタート
idColumn.AutoIncrementStep = 1; // 正の方向へ連番
idColumn.AutoIncrement = true;

この「先出し最大値+1」の処理を挟むことで、画面上のソート順を維持したまま、重複エラーを回避することができます。
なお、画面上で maxId + 1 の値が付与されても、CommandBuilder はこれが自動採番列であることを知っているため、Update を実行した際には INSERT 文から Id 列を除外します。

まとめ

今回は、DataGridView へのデータ表示から、画面上での変更内容をボタンクリックで一括保存する SQLite 連携方法について解説しました。
「画面の見た目を変更すれば、データとデータベースも連動して変わる」という Windows Forms 本来のバインディングの強みを活かした画面開発の参考になりましたら幸いです。


💬 コメント