maui-sqlite-database について
.NET MAUI アプリで SQLite を扱うための実践的なスキルです。接続のライフサイクルやパス、並行処理、性能に関する注意点をまとめています。
主な機能
- 01クロスプラットフォームのDBパス案内
- 02シングルトン接続と遅延初期化
- 03WALモードと並行処理の指針
- 04安全なデータベースファイル操作
- 05クエリと一括書き込みの最適化
活用シーン
- 01MAUI各プラットフォームのDBパス選定
- 02重複接続による破損の防止
- 03一括書き込みと大規模テーブルのクエリ計画
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-sqlite-databaseインストール後、Claude Code で/maui-sqlite-databaseを入力して読み込まれたか確認します。
同梱ファイルreferences/sqlite-database-api.md
詳細
SQLite データベース — 落とし穴とベストプラクティス
サービスの実装全体、定数、データモデルのテンプレート、一般的なパターンについては、references/sqlite-database-api.md を参照してください。
⚠️ パッケージ選択の落とし穴
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="sqlite-net" />
<PackageReference Include="SQLitePCL.raw" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.*" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="2.1.*" />
よくある間違い
❌ データベースのパスに Environment.GetFolderPath を使用する
// ❌ Not cross-platform safe — fails on some MAUI targets
var path = Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData), "app.db3");
// ✅ Use FileSystem.AppDataDirectory for all MAUI platforms
var path = Path.Combine(FileSystem.AppDataDirectory, "app.db3");
❌ 複数の SQLiteAsyncConnection インスタンスを使用する
SQLiteAsyncConnection は、同じファイルを指す複数のインスタンス間ではスレッドセーフではありません。DI のシングルトンを通じて、単一のインスタンスを使用してください。
// ❌ Creating new connections per request
public async Task<List<Item>> GetItems()
{
var db = new SQLiteAsyncConnection(Constants.DatabasePath);
return await db.Table<Item>().ToListAsync();
}
// ✅ Lazy singleton — one connection, created once
private SQLiteAsyncConnection? _database;
private async Task<SQLiteAsyncConnection> GetDatabaseAsync()
{
if (_database is not null) return _database;
_database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
await _database.ExecuteAsync("PRAGMA journal_mode=WAL;");
await _database.CreateTableAsync<TodoItem>();
return _database;
}
❌ WAL モードを有効にし忘れる
WAL を有効にしないと、読み取り側が書き込み側をブロックします。初期化時に必ず有効にしてください。
await _database.ExecuteAsync("PRAGMA journal_mode=WAL;");
❌ データベースを開いたままファイル操作を行う
// ❌ Moving/deleting while connection is open — data corruption
File.Delete(Constants.DatabasePath);
// ✅ Always close first
await databaseService.CloseConnectionAsync();
if (File.Exists(Constants.DatabasePath))
File.Delete(Constants.DatabasePath);
プラットフォーム固有の注意点
| プラットフォーム | 注意点 |
|---|---|
| iOS | FileSystem.AppDataDirectory は iCloud のバックアップ対象です。データベースを iCloud バックアップの対象外にするには、FileSystem.CacheDirectory を使用してください |
| すべて | 同じファイルに対して複数の SQLiteAsyncConnection インスタンスを使用すると、データが破損します |
| すべて | WAL が無効だと、読み取り側が書き込み側をブロックし、同時実行時のパフォーマンスが低下します |
| すべて | データベースを開いたままファイル操作を行うと、データが破損します |
判断の指針
| 質問 | 推奨事項 |
|---|---|
| DI のライフタイムは? | シングルトン — 接続は 1 つにし、同時読み取りは WAL で処理します |
| WAL モードを有効にする? | 必ず有効にする — モバイルでは無効にする理由はありません |
| データベースのパスは? | FileSystem.AppDataDirectory — Environment.GetFolderPath は使用しないでください |
| 保存パターンは? | Id != 0 を確認し、真なら Update、そうでなければ Insert |
| 複数のテーブルが必要? | 遅延初期化に CreateTableAsync<T>() の呼び出しをすべて追加します |
| エクスポート/バックアップが必要? | 先に接続を閉じてから、File.Copy を実行します |
パフォーマンスに関するヒント
- 一括書き込みにはトランザクションを使用する — 個別の挿入は遅いため、
RunInTransactionAsyncで囲みます - 頻繁にクエリする列に
[Indexed]を追加する — 特に外部キーに追加します - WAL モードにより、読み取りと書き込みの競合を解消できます
- 大きなテーブルでは
ToListAsync()を避ける —Where()によるフィルタリングとページネーションを使用します - 複雑なクエリには生の SQL を使用する — 結合では、
QueryAsync<T>のほうが LINQ の連結より高速です
チェックリスト
-
sqlite-net-pclとSQLitePCLRaw.bundle_greenをインストールする(Microsoft.Data.Sqliteではありません) - データベースのパスに
FileSystem.AppDataDirectoryを使用する - モデルに
[PrimaryKey, AutoIncrement]を指定する - 遅延非同期初期化パターンを使う
DatabaseServiceを 1 つだけ用意する -
PRAGMA journal_mode=WALで WAL を有効にする - DI で
DatabaseServiceをシングルトンとして登録する - ファイルの移動/コピー/削除を行う前に接続を閉じる
- iOS:必要に応じて、データベースを iCloud バックアップの対象外にする
コメント
Markdown 対応