SQLiteは簡易的なデータベースとして扱うので、データ容量は小さく軽量で処理が早いという特徴があります。
アプリケーションに組み込んで使用することが多いデータベースですが、運用が長くなれば長くなるほど、データが蓄積されます。処理が早いとは言え、値を取得するまでに時間がかかる場合があるかもしれません。
シングルスレッド(同期処理)で実行すると、データベースの処理が終わるまで他のタスクを行う事ができなくなります。ユーザーからすると、画面の操作ができない状態となり、フリーズしたと勘違いされるかもしれません。
このような事態を防ぐために、非同期処理でSQLiteを実装するといいかもしれませんね。
この記事ではSQLiteを非同期で実行する方法を紹介していますので、ぜひ最後まで読んでみてください。
SQLite(エスキューライト)とは
ここでSQLiteについて軽く説明をします。
SQLiteとはパブリックドメインで誰でも無料で使える、軽量のリレーショナルデータベースマネージメントシステム(RDMBS)です。
サーバー上にあるデータベースではなく、アプリケーションに組み込んで利用される事が多いデータベースです。ローカルフォルダにデータベースの本体があるので、大量のクエリーを実行しても直接読み書きする構造なので通信によるボトルネックが発生せず、高速で安定したデータサービスを提供します。
また、公式サイトでは「2050年まで開発者がSQLiteのサポートをする」と記載されています。
[jin-fusen3 text=”SQLiteの特徴”]
- パブリックドメインとして提供されている。
- 軽量で導入がしやすく、インストールが簡単である。
- SQLのデータベース言語を使用できる。
- データを1つのファイルとして格納できる。
- PCのみならず、スマートフォンなど様々なOSに対応している。(マルチプラットフォーム)
上記の特徴がある一方で、SQLiteはローカルのファイルを直接データベースとして扱うので、複数のサーバーとネットワーク越しに接続するのには不向きです。
こういう用途でデータベースを利用したい場合は MySQL や PostgreSQL など複数接続に向いているデータベースを使いましょう。
非同期でSQLiteを使う方法
前回、SQLiteを同期で実行する方法を記述しました。SQLiteの詳しい記述方法については、以下の記事を確認してみてください。
この同期処理を非同期処理に書き換えていきます。
クエリー実行
まず、C#で非同期処理を実行するには「async / await」を扱います。
非同期メソッドの呼び出し側のメソッドでは「async」キーワードを追加して、非同期メソッドとして定義します。
非同期メソッドの呼び出し時には「await」キーワードをメソッド名の前に記述します。こうすることで非同期をしているタスクが完了するまで待つことができます。タスクの完了を待っている間はメインスレッドへ戻ります。これにより重たい処理を行っても画面がフリーズする事はありません。
SQLiteには非同期で処理をするメソッドが用意されていますので、そのメソッドへ書き換えることで非同期で処理を行うことができます。
private async Task ExecuteNonQueryAsync(string query)
{
try
{
// 接続先を指定
using (var conn = new SQLiteConnection("Data Source=DataBase.sqlite"))
using (var command = conn.CreateCommand())
{
// 接続
await conn.OpenAsync();
// コマンドの実行処理
command.CommandText = query;
await command.ExecuteNonQueryAsync();
//var value = await command.ExecuteNonQueryAsync();
//MessageBox.Show($"更新されたレコード数は {value} です。");
}
}
catch (Exception ex)
{
//例外が発生した時はメッセージボックスを表示
MessageBox.Show(ex.Message);
}
}
ここでは、SQLiteへ接続をする Openメソッド を OpenAsync メソッドにし、コマンドを実行するメソッドを ExcuteNonQuery メソッドを ExcuteNonQueryAsync メソッドにしています。
メソッド名の後ろには、非同期のメソッドであることを認識しやすくする為に「Async」というキーワードが付いています。
このキーワードがあるメソッドの前には「await」を付けておきましょう。
コマンドを実行する ExecuteNonQueryAsync() は、INSERT・UPDATE・DELETEのような結果を返さないSQL文を実行する場合には、ExecuteNonQueryメソッドを使用します。
テーブルの作成
CREATE文を使って非同期でテーブルを作成します。クエリーのルールは以下です。
[jin-fusen3 text=”SQL文の書き方”]
CREATE TABLE IF NOT EXISTS [テーブル名] (カラム1,カラム2,primary key (カラム1))
ExecuteNonQueryAsync() でクエリーを実行します。
private async Task CreateTableAsync()
{
// テーブル名が存在しなければ作成する
var query = "CREATE TABLE IF NOT EXISTS SAMPLE_TABLE (" +
"NO INTEGER NOT NULL," +
"DATETIME TEXT NOT NULL," +
"NAME TEXT NOT NULL," +
"primary key (NO))";
// クエリー実行
await ExecuteNonQueryAsync(query);
}
データの登録
INSERT文を使って非同期でデータを登録します。クエリーのルールは以下です。
[jin-fusen3 text=”SQL文の書き方”]
INSERT INTO [テーブル名] (カラム1,カラム2,カラム3) VALUES (データ1,データ2,データ3)
ExecuteNonQueryAsync() でクエリーを実行します。
private async Task InsertRecordAsync(string datetime, string name)
{
// レコードの登録
var query = $"INSERT INTO SAMPLE_TABLE (DATETIME,NAME) VALUES ('{datetime}','{name}')";
// クエリー実行
await ExecuteNonQueryAsync(query);
}
この関数を使って非同期でレコードを登録してみます。
await InsertRecordAsync(DateTime.Now.AddDays(0).ToString("yyyy-MM-dd"), "coffee");
await InsertRecordAsync(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd"), "meet");
await InsertRecordAsync(DateTime.Now.AddDays(2).ToString("yyyy-MM-dd"), "snacks");
await InsertRecordAsync(DateTime.Now.AddDays(3).ToString("yyyy-MM-dd"), "juice");
SQLiteに登録された値を見る「DB Browser for SQLite」というツールがあります。簡単にインストールできるので使ってみてください。
登録されたデータを確認してみましょう。
[jin-img-waku]
[/jin-img-waku]
ちゃんと値が登録されていますね。
データの検索
SELECT文を使って登録されているデータを非同期で検索をします。データを検索するクエリーのルールは以下です。
[jin-fusen3 text=”SQL文の書き方”]
SELECT * FROM [テーブル名] WHERE 検索条件 ORDER BY カラム ASC or DESC;
ExecuteReaderAsync() でクエリーを実行します。
private async Task<List<Datas>> SerachRecordDataAsync(string column, string word)
{
// 検索条件
var query = $"SELECT * FROM SAMPLE_TABLE WHERE {column} = '{word}' ORDER BY NO ASC";
var result = new List<Datas>();
// 接続先を指定
using (var conn = new SQLiteConnection("Data Source=DataBase.sqlite"))
using (var command = conn.CreateCommand())
{
// 接続
await conn.OpenAsync();
// コマンドの実行処理
command.CommandText = query;
using (var reader = (SQLiteDataReader)await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
result.Add(new Datas(
(Int64)reader["NO"],
(string)reader["DATETIME"],
(string)reader["NAME"]));
}
}
}
return result;
}
record Datas(Int64 no, string dateTime, string name);
ExecuteReaderAsync() の戻り値は条件に一致したレコードです。ここでは 条件に一致したレコードを全て List<Datas> へ追加しています。
この関数を使ってテーブルに登録されているデータを検索する例が以下になります。
var result = await SerachRecordDataAsync("NO", "1");
Debug.WriteLine(result[0].name);
レコード(データ)の更新
UPDATE文を使って、非同期で登録されているデータを新しいデータに更新します。データを更新するクエリーのルールは以下です。
[jin-fusen3 text=”SQL文の書き方”]
UPDATE [テーブル名] SET カラム1 = 値1, カラム2 = 値2, … WHERE 条件式;
ExecuteNonQueryAsync() でクエリーを実行します。
private async Task UpdateRecordAsync(int no, string datetime, string name)
{
// レコードの登録
var query = $"UPDATE SAMPLE_TABLE SET DATETIME = '{datetime}', NAME = '{name}' WHERE NO = {no};";
// クエリー実行
await ExecuteNonQueryAsync(query);
}
この関数をテーブルのレコードのデータを更新する例が以下になります。
await UpdateRecordAsync(3, DateTime.Now.ToString("yyyy-MM-dd"), "tea");
レコードの削除
DELETE文を使って非同期でテーブルに登録されているレコードを削除します。レコードを削除するクエリーのルールは以下です。
[jin-fusen3 text=”SQL文の書き方”]
DELETE FROM [テーブル名] WHERE 検索条件;
ExecuteNonQueryAsync() でクエリーを実行します。
private async Task DeleteRecordAsync(string column, string word)
{
// レコードの削除
var query = $"DELETE FROM SAMPLE_TABLE WHERE {column} = '{word}'";
// クエリー実行
await ExecuteNonQueryAsync(query);
}
この関数をテーブルのレコードのデータを更新する例が以下になります。
await DeleteRecordAsync("NAME", "coffee");
テーブルの削除
DROP文を使って非同期でテーブルを削除します。テーブルを削除するクエリーのルールは以下です。
[jin-fusen3 text=”SQL文の書き方”]
DELETE FROM [テーブル名] WHERE 検索条件;
ExecuteNonQueryAsync() でクエリーを実行します。
private async Task DropTableAsync()
{
// テーブルの削除
var query = "DROP TABLE SAMPLE_TABLE";
// クエリー実行
await ExecuteNonQueryAsync(query.ToString());
}
まとめ
この記事ではC#でSQLiteを非同期で処理する方法を紹介しました。
SQLiteのライブラリには元々、非同期で処理をする為のメソッドが用意されているので、「async / await」のルールを理解していれば簡単に実装することができます。
「あれ!?非同期で処理するには??」と疑問に思った時に、この記事を参考にして頂ければ幸いです。
以上、最後まで読んで頂きありがとうございました。