.NET C#プロジェクトで大量のデータ扱う時に、気軽にSQLでデータを操作したいときに、**SQLite** は、選択肢として適してるかと思い、基本的な操作について調べてみたので、以下まとめてみます。
1. SQLite を採用するメリット
SQLite はサーバーレスで動作するリレーショナルデータベースで、以下の特性を持ちます。
・運用の簡略化
DBサーバーの構築が不要。データベース全体が単一のファイル(`.db`)として完結。
・ポータビリティ
実行バイナリとDBファイルを配布するだけで動作し、環境依存が極めて少ない。
・扱いやすさ
標準的なSQLをサポートしており、将来的に他のRDBMS(MySQL, PostgreSQL等)へ移行する際の敷居も低い。
2. 開発環境のセットアップ
.NET環境では、公式ライブラリである `Microsoft.Data.Sqlite` が利用できます。
3. 基本実装:接続とスキーマ定義
データベースへの接続には `SqliteConnection` を使用します。
using Microsoft.Data.Sqlite;
// 接続文字列の定義
var connectionString = "Data Source=sample.db";
using (var connection = new SqliteConnection(connectionString))
{
connection.Open();
// テーブル定義の実行
var createTableSql = @"
CREATE TABLE IF NOT EXISTS Users (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Age INTEGER
)";
using var command = new SqliteCommand(createTableSql, connection);
command.ExecuteNonQuery();
}
`using` ブロックを抜ける際に `Dispose()` が呼ばれ、接続が確実に閉じられるようにしています。4. データ操作:安全性とパラメータ利用
SQLインジェクション脆弱性を排除するため、文字列結合を避け、**パラメータ(プレースホルダー)**を利用します。
var insertSql = "INSERT INTO Users (Name, Age) VALUES ($name, $age)";
using var command = new SqliteCommand(insertSql, connection);
// パラメータによる安全な値のセット
command.Parameters.AddWithValue("$name", "田中太郎");
command.Parameters.AddWithValue("$age", 25);
command.ExecuteNonQuery();
5. データの読み出し (SELECT)
データの検索や取得には `ExecuteReader` を使用します。実行結果として返される `SqliteDataReader` を反復処理することで、各行のデータにアクセスできます。
var selectSql = "SELECT Id, Name, Age FROM Users WHERE Age >= $age";
using var command = new SqliteCommand(selectSql, connection);
command.Parameters.AddWithValue("$age", 20);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
// 序数(インデックス)指定による型安全なデータ取得
var id = reader.GetInt32(0);
var name = reader.GetString(1);
var age = reader.GetInt32(2);
Debug.WriteLine($"ID: {id}, Name: {name}, Age: {age}");
}
}
ExecuteReader
複数行の結果セットを取得する際に使用。
Read() メソッド
次の行が存在する場合は `true` を返し、カーソルを一行進める。
型安全な取得
`GetInt32` や `GetString` メソッドを利用することで、キャストによるランタイムエラーのリスクを低減できます。
6. パフォーマンス最適化:トランザクションの活用
SQLite はデフォルトで「自動コミットモード」になっており、1命令ごとにディスクI/Oが発生します。なので、大量のデータを逐次挿入すると著しく低速になります。数千件規模のデータを扱う場合は、**トランザクション**を用いて書き込みをバッチ化することで、劇的な高速化が可能です。
// トランザクションの明示的な開始
using var transaction = connection.BeginTransaction();
try
{
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "INSERT INTO Users (Name, Age) VALUES ($name, $age)";
for (int i = 0; i < 10000; i++)
{
command.Parameters.Clear();
command.Parameters.AddWithValue("$name", $"User_{i}");
command.Parameters.AddWithValue("$age", 20);
command.ExecuteNonQuery();
}
// 全ての処理が成功した場合に確定
transaction.Commit();
}
catch (Exception)
{
// エラー発生時はロールバックを行い、データのアトミック性を担保
transaction.Rollback();
throw;
}
結論と今後の展望
SQLiteの導入障壁は想像以上に低く、その軽量さと柔軟性は特筆に値します。これまでインメモリ(Dictionary型とLINQの組み合わせ)で管理していたキャッシュ情報などを、使い慣れたSQLによる操作へと置き換えられる点は、設計上の大きなアドバンテージです。
今後は、データの複雑化に備え、インデックス設計によるさらなる高速化や、マルチスレッド環境下での適切な同時実行制御(WALモードの活用など)について検討を深めていく予定です。

