[.NET MAUI] SQLiteデータベース利用
レンタカー予約画面の都道府県入力用リストの都道府県データを、SQLiteのデータベースから読み込んで生成するようにしたい。
SQLiteデータベースの作成
今回は都道府県のデータが入っているRegion.db3
というSQLiteのデータベースをあらかじめ作成し、アプリにバンドルする形式とする。
まずは準備として、あとでテーブルにインポートするためのCSVを準備しておく。
1,北海道
2,青森県
...
47,沖縄県
次に、コマンドラインでRegion.db3
を作成する。macならSQLiteのコマンドラインツールが標準搭載されている。下記の通りRegion
という名前のテーブルを作成し、Region.csv
からインポートする。
% cd ~/Projects/MyProto/Resources/Raw/
% sqlite3 Region.db3
sqlite> create table Region ( Code integer, Name text );
sqlite> .separator ','
sqlite> .import region.csv Region
sqlite> select * from Region;
...
sqlite> .exit
SQLiteでのデータベース接続
.NET MAUIアプリケーションでSQLiteを使用するためには「ツール > NuGetパッケージの管理」から下記2つのパッケージをインストールする必要がある。
- sqlite-net-pcl
- SQLitePCLRaw.bundle-green
後者が入っていないとMacCatalystまたはiOSでDBアクセス時にSystem.TypeInitializationException
例外スローされ、ここでだいぶハマった。公式サイトでもちゃんと書いてあるので、よく読めということ。
都道府県リストの生成処理は、最小限のコードで書くならこんな感じ。
Region.db3
データベースと接続後、Region
テーブルからToListAsync
メソッドで全項目を取得し、Region
クラスのListに型変換する。
+using SQLite;
...
// 都道府県リストの生成
filePath = NSBundle.MainBundle.ResourcePath;
filePath = Path.Combine(filePath, "Raw");
- filePath = Path.Combine(filePath, "region.json");
- json = File.ReadAllText(filePath);
- var rlst = JsonSerializer.Deserialize<List<Region>>(json);
+ filePath = Path.Combine(filePath, "Region.db3");
+ SQLiteAsyncConnection dbConn = new SQLiteAsyncConnection(filePath);
+ var rlst = await dbConn.Table<Region>().ToListAsync();
+ dbConn.GetConnection().Close();
+ dbConn.GetConnection().Dispose();
foreach (Region r in rlst)
{
RegionListFlexLayout.Children.Add(
new Button { Text = r.Name }
);
}
デバッグ実行してみると、めでたくSQLiteのDBから読み込んで都道府県入力用リストを生成できた!
テーブル名
さきほどの最小限のコードでは、データベース接続後にテーブル名を指定せずとも普通にToListAsync
でRegion
型のListを取ってこれたあたりが不思議である。
テーブルの構造はこちらのpublic class Region
の部分で定義したクラスの構造と一致しているが、テーブル名までは決めた記憶はないぞ?
どうやらテーブル名は、テーブルの構造を定義したクラス名(今回の場合はRegion
)と一致している必要があるようだ。ためしにRegion
クラスの先頭に[Table]
属性をつけて明示的にテーブル名を変えるとSQLite.SQLiteException: no such table: RegionTable
例外スローされた。
+[Table("RegionTable")]
public class Region
{
public int Code { get; set; } = 0;
public string Name { get; set; } = "";
}
なお、テーブルの構造を定義するクラスでは他にも下記のような属性の使い方があるもよう。これでCreateTableAsync<Region>()
でテーブルを新規に作ると、列にユニーク制約や最大長指定ができるようになる。
public class Region
{
+ [Unique]
public int Code { get; set; } = 0;
+ [MaxLength(20)]
public string Name { get; set; } = "";
}
DBアクセス用クラス
今回は最小限のソースでSQLiteデータベース接続を実現してみたが、いくつかのサンプルコードを読むと、データベース接続用クラスを作って呼び出す形式が一般的である。
public class RegionDB
{
SQLiteAsyncConnection _dbConn;
public RegionDB()
{
}
private async Task Init()
{
if (_dbConn is not null)
return;
// DB接続
string filePath = NSBundle.MainBundle.ResourcePath;
filePath = Path.Combine(filePath, "Raw");
filePath = Path.Combine(filePath, "Region.db3");
_dbConn = new SQLiteAsyncConnection(filePath);
var result = await _dbConn.CreateTableAsync<Region>();
}
// テーブル内の全項目を返す
public async Task<List<Region>> GetAllRegions()
{
await Init();
return await _dbConn.Table<Region>().ToListAsync();
}
}
RegionDBクラスのインスタンスを作ってアクセスする処理はこのような感じ。
var db = new RegionDB();
var rlst = await db.GetAllRegions();