Xamarin.Forms ローカル データベース

Download Sampleサンプルのダウンロード

SQLite データベース エンジンを使うと、Xamarin.Forms アプリケーションは共有コードでデータ オブジェクトを読み込んで保存できます。 サンプル アプリケーションでは、SQLite データベース テーブルを使って todo 項目を格納します。 この記事では、共有コードで SQLite.Net を使って、ローカル データベースに情報を格納および取得する方法について説明します。

Screenshots of the Todolist app on iOS and Android

次の手順に従って、SQLite.NET をモバイル アプリに統合します。

  1. NuGet パッケージをインストールします
  2. 定数を構成します
  3. データベース アクセス クラスを作成します
  4. Xamarin.Forms 内のデータにアクセスする
  5. 詳細設定。

SQLite NuGet パッケージをインストールする

NuGet パッケージ マネージャーを使って sqlite-net-pcl を検索し、最新バージョンを共有コード プロジェクトに追加します。

類似した名前を持つ NuGet パッケージが多数あります。 正しいパッケージには、次の属性があります。

  • ID: sqlite-net-pcl
  • 作成者: SQLite-net
  • 所有者: praeclarum
  • NuGet リンク:sqlite-net-pcl

パッケージ名に関係なく、sqlite-net-pcl NuGet パッケージを .NET Standard プロジェクトでも使用します。

重要

SQLite.NET は、praeclarum/sqlite-net リポジトリからサポートされているサードパーティ製ライブラリです。

アプリ定数を構成する

サンプル プロジェクトには、共通の構成データを提供する Constants.cs ファイルが含まれます。

public static class Constants
{
    public const string DatabaseFilename = "TodoSQLite.db3";

    public const SQLite.SQLiteOpenFlags Flags =
        // open the database in read/write mode
        SQLite.SQLiteOpenFlags.ReadWrite |
        // create the database if it doesn't exist
        SQLite.SQLiteOpenFlags.Create |
        // enable multi-threaded database access
        SQLite.SQLiteOpenFlags.SharedCache;

    public static string DatabasePath
    {
        get
        {
            var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            return Path.Combine(basePath, DatabaseFilename);
        }
    }
}

定数ファイルは、データベース接続の初期化に使われる既定の SQLiteOpenFlag 列挙値を指定します。 SQLiteOpenFlag 列挙型では、次の値をサポートしています。

  • Create: データベース ファイルが存在しない場合、接続によって自動的に作成されます。
  • FullMutex: シリアル化されたスレッド モードで接続が開かれます。
  • NoMutex:接続はマルチスレッド モードで開かれます。
  • PrivateCache: 接続が有効になっている場合でも、接続は共有キャッシュに関与しません。
  • ReadWrite: 接続によりデータの読み取りと書き込みが行われます。
  • SharedCache: 接続が有効になっている場合、接続は共有キャッシュに関与します。
  • ProtectionComplete: デバイスがロックされている間、ファイルは暗号化され、アクセスできません。
  • ProtectionCompleteUnlessOpen: ファイルは開くまで暗号化されますが、ユーザーがデバイスをロックした場合でもアクセスできます。
  • ProtectionCompleteUntilFirstUserAuthentication: ファイルは、ユーザーがデバイスを起動してロックを解除するまで暗号化されます。
  • ProtectionNone: データベース ファイルは暗号化されていません。

データベースの使用方法に応じて、異なるフラグを指定する必要がある可能性があります。 SQLiteOpenFlags の詳細については、sqlite.org で「新しいデータベース接続を開く」をご覧ください。

データベース アクセス クラスを作成する

データベース ラッパー クラスは、アプリの残りの部分からデータ アクセス層を抽象化します。 このクラスで、クエリ ロジックが一元化され、データベース初期化の管理が簡素化されるので、アプリの規模拡大に合わせてデータ操作をリファクタリングしたり拡張したりしやすくなります。 Todo アプリは、この目的のために TodoItemDatabase クラスを定義します。

遅延初期化

TodoItemDatabase は、カスタム AsyncLazy<T> クラスで表される非同期遅延初期化を使って、最初にアクセスされるまでデータベースの初期化を遅らせます。

public class TodoItemDatabase
{
    static SQLiteAsyncConnection Database;

    public static readonly AsyncLazy<TodoItemDatabase> Instance = new AsyncLazy<TodoItemDatabase>(async () =>
    {
        var instance = new TodoItemDatabase();
        CreateTableResult result = await Database.CreateTableAsync<TodoItem>();
        return instance;
    });

    public TodoItemDatabase()
    {
        Database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
    }

    //...
}

Instance フィールドは、TodoItem オブジェクトのデータベース テーブルを作成するために使われ (まだ存在しない場合)、TodoItemDatabase をシングルトンとして返します。 AsyncLazy<TodoItemDatabase> 型の Instance フィールドは、最初に待機されるときに構築されます。 複数のスレッドがフィールドに同時にアクセスしようとすると、それらはすべて単一の構築を使用します。 その後、構築が完了すると、すべての await 操作が完了します。 さらに、構築完了後の await 操作は、値が使用可能になるため、直ちに続行されます。

Note

データベース接続は静的フィールドであり、アプリの存続期間中は単一データベース接続が使われることが保証されます。 永続的な静的接続を使うと、1 つのアプリ セッション中に接続を複数回開いたり閉じたりするよりもパフォーマンスが向上します。

非同期遅延初期化

データベースの初期化を開始し、実行のブロックを回避し、例外をキャッチする機会を得るために、サンプル アプリケーションでは、AsyncLazy<T> クラスで表される非同期遅延初期化が使われます。

public class AsyncLazy<T>
{
    readonly Lazy<Task<T>> instance;

    public AsyncLazy(Func<T> factory)
    {
        instance = new Lazy<Task<T>>(() => Task.Run(factory));
    }

    public AsyncLazy(Func<Task<T>> factory)
    {
        instance = new Lazy<Task<T>>(() => Task.Run(factory));
    }

    public TaskAwaiter<T> GetAwaiter()
    {
        return instance.Value.GetAwaiter();
    }
}

AsyncLazy クラスは、Lazy<T> 型と Task<T> 型を組み合わせて、リソースの初期化を表す遅延初期化タスクを作成します。 コンストラクターに渡されるファクトリ デリゲートは、同期または非同期のいずれかにできます。 ファクトリ デリゲートはスレッド プール スレッド上で実行され、(複数のスレッドが同時に開始しようとした場合でも) 複数回実行されることはありません。 ファクトリ デリゲートが完了すると、遅延初期化された値が使用可能になり、AsyncLazy<T> インスタンスを待機しているメソッドはその値を受け取ります。 詳しくは、「AsyncLazy」をご覧ください。

データ操作メソッド

TodoItemDatabase クラスには、作成、読み取り、編集、削除の 4 種類のデータ操作を行うメソッドが含まれています。 SQLite.NET ライブラリには、SQL ステートメントを記述せずにオブジェクトを格納して取得できる単純なオブジェクト リレーショナル マップ (ORM) を備えています。

public class TodoItemDatabase
{
    // ...
    public Task<List<TodoItem>> GetItemsAsync()
    {
        return Database.Table<TodoItem>().ToListAsync();
    }

    public Task<List<TodoItem>> GetItemsNotDoneAsync()
    {
        // SQL queries are also possible
        return Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
    }

    public Task<TodoItem> GetItemAsync(int id)
    {
        return Database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
    }

    public Task<int> SaveItemAsync(TodoItem item)
    {
        if (item.ID != 0)
        {
            return Database.UpdateAsync(item);
        }
        else
        {
            return Database.InsertAsync(item);
        }
    }

    public Task<int> DeleteItemAsync(TodoItem item)
    {
        return Database.DeleteAsync(item);
    }
}

Xamarin.Forms 内のデータにアクセスする

TodoItemDatabase クラスは Instance フィールドを公開します。それを通じて TodoItemDatabase クラスのデータ アクセス操作を呼び出すことができます。

async void OnSaveClicked(object sender, EventArgs e)
{
    var todoItem = (TodoItem)BindingContext;
    TodoItemDatabase database = await TodoItemDatabase.Instance;
    await database.SaveItemAsync(todoItem);

    // Navigate backwards
    await Navigation.PopAsync();
}

詳細な構成

SQLite には、この記事とサンプル アプリで説明されているよりも多くの機能を備えた堅牢な API が用意されています。 次のセクションでは、スケーラビリティの点で重要な機能について説明します。

詳細については、sqlite. org に掲載されている SQLite ドキュメントをご覧ください。

先書きログ

既定では、SQLite は従来のロールバック ジャーナルを使用します。 変更されていないデータベース コンテンツのコピーが別個のロールバック ファイルに書き込まれた後、変更内容がデータベース ファイルに直接書き込まれます。 COMMIT は、ロールバック ジャーナルが削除されると発生します。

先書きログ (WAL) は、最初に別個の WAL ファイルに変更内容を書き込みます。 WAL モードでは、COMMIT は、WAL ファイルに追加される特別なレコードです。これにより、1 つの WAL ファイルで複数のトランザクションを記録できます。 WAL ファイルは、チェックポイントと呼ばれる特別な操作で、元のデータベース ファイルにマージされます。

読み取り操作と書き込み操作を同時に実行できるように、リーダーとライターが互いをブロックしないので、ローカル データベースでは WAL の方が高速になります。 ただし、WAL モードでは、ページ サイズの変更を許可せず、データベースにファイルの関連付けを追加し、追加のチェックポイント操作を追加します。

SQLite.NET で WAL を有効にするには、SQLiteAsyncConnection インスタンスで EnableWriteAheadLoggingAsync メソッドを呼び出します。

await Database.EnableWriteAheadLoggingAsync();

詳細については、sqlite.org に掲載されている「SQLite 先書きログ」をご覧ください。

データベースをコピーする

SQLite データベースのコピーが必要になる場合が、次のようにいくつかあります。

  • データベースが付属しているアプリケーションを、モバイル デバイス上の書き込み可能ストレージにコピーまたは移動する必要がある場合。
  • データベースのバックアップまたはコピーを作成する必要がある場合。
  • データベース ファイルのバージョン管理、移動、名前変更が必要な場合。

一般に、データベース ファイルの移動、名前変更、コピーは、他のファイルの種類と同じプロセスですが、いくつかの追加の考慮事項があります。

  • データベース ファイルを移動する前に、すべてのデータベース接続を閉じる必要があります。
  • 先書きログを使用する場合、SQLite は共有メモリ アクセス (.shm) ファイルと先書きログ (.wal) ファイルを作成します。 必ず、これらのファイルにも変更を適用してください。

詳細については、「Xamarin.Forms でのファイル処理」を参照してください。