Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

SQLite3 のマイグレーションを自前でシンプルに実装する【C#】

0
Posted at

はじめに

僕のお仕事では、担当の方にデスクトップアプリの EXE だけ送って、現場で入れ替えてもらうことも多いです。そんな時に使用する SQLite3 のマイグレーション処理を自前でシンプルに実装した際の備忘録です。

改訂履歴

  • 2026/08/24 : 初版公開。

本文

1. 環境

  • .NET 8.0
  • Microsoft.Data.Sqlite.Core 10.0.11
  • SQLite3 3.53.3
  • SQLitePCLRaw.bundle_e_sqlite3 2.1.12
  • Visual Studio Community 2022

2. 実装

SQLiteManager クラスに Migrate() を実装しました。

SQLiteManager.cs
using Microsoft.Data.Sqlite;

namespace SQLiteMigrationExample;

/// <summary>
/// SQLite データベースの管理を行います。
/// </summary>
public static class SQLiteManager
{
    private static readonly int s_currentVersion = 3;
    private static readonly string s_connectionString = "Data Source=DataBase.sqlite";

    /// <summary>
    /// データベースをマイグレーションします。
    /// </summary>
    /// <returns>非同期操作を表すタスク。マイグレーションに成功した場合は <see langword="true"/>、それ以外の場合は <see langword="false"/>。</returns>
    public static async Task<bool> Migrate()
    {
        try
        {
            await using var connection = new SqliteConnection(s_connectionString);
            await connection.OpenAsync();

            // SQLite のスキーマバージョンを取得する。
            await using SqliteCommand getCommand = connection.CreateCommand();
            getCommand.CommandText = "PRAGMA user_version;";
            var result = await getCommand.ExecuteScalarAsync();
            if (result is null)
            {
                // ※必要に応じて異常処理を実装してください。
                return false;
            }

            var version = Convert.ToInt32(result);

            // マイグレーションを行う。
            while (version < s_currentVersion)
            {
                using SqliteTransaction transaction = connection.BeginTransaction();

                try
                {
                    switch (version)
                    {
                        case 0:
                            await Migration1(connection, transaction);
                            version = 1;
                            break;

                        case 1:
                            await Migration2(connection, transaction);
                            version = 2;
                            break;

                        case 2:
                            await Migration3(connection, transaction);
                            version = 3;
                            break;

                        default:
                            // ※必要に応じて異常処理を実装してください。
                            return false;
                    }

                    // SQLite のスキーマバージョンを更新する。
                    await using SqliteCommand setCommand = connection.CreateCommand();
                    setCommand.Transaction = transaction;
                    setCommand.CommandText = $"PRAGMA user_version = {version};";
                    await setCommand.ExecuteNonQueryAsync();

                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            }

            return true;
        }
        catch
        {
            // ※必要に応じて異常処理を実装してください。
            return false;
        }
    }

    private static async Task Migration1(SqliteConnection connection, SqliteTransaction transaction)
    {
        await using SqliteCommand command = connection.CreateCommand();

        command.Transaction = transaction;

        command.CommandText = """
            CREATE TABLE Users
            (
                Id   INTEGER PRIMARY KEY AUTOINCREMENT,
                Name TEXT NOT NULL
            );
            """;

        await command.ExecuteNonQueryAsync();
    }

    private static async Task Migration2(SqliteConnection connection, SqliteTransaction transaction)
    {
        await using SqliteCommand command = connection.CreateCommand();

        command.Transaction = transaction;

        command.CommandText = """
            ALTER TABLE Users
            ADD COLUMN Email TEXT;
            """;

        await command.ExecuteNonQueryAsync();
    }

    private static async Task Migration3(SqliteConnection connection, SqliteTransaction transaction)
    {
        await using SqliteCommand command = connection.CreateCommand();

        command.Transaction = transaction;

        command.CommandText = """
            CREATE TABLE Orders
            (
                Id        INTEGER PRIMARY KEY AUTOINCREMENT,
                UserId    INTEGER NOT NULL,
                OrderDate TEXT NOT NULL
            );
            """;

        await command.ExecuteNonQueryAsync();
    }
}

3. 使用例

アプリケーションのエントリポイントで Migrate() を呼びます。実戦の場合、マイグレーションの前にバックアップを取得した方が良いです。

Program.cs
namespace SQLiteMigrationExample;

/// <summary>
/// アプリケーションのエントリポイントを定義します。
/// </summary>
internal static class Program
{
    /// <summary>
    /// アプリケーションのエントリポイントです。
    /// </summary>
    /// <returns>非同期操作を表すタスク。</returns>
    [STAThread]
    private static async Task Main()
    {
        // マイグレーションを実行する。
        if (!await SQLiteManager.Migrate())
        {
            // ※必要に応じて異常処理を実装してください。
            MessageBox.Show("Failed to migrate the database.");
        }

        // アプリケーションを実行する。
        ApplicationConfiguration.Initialize();
        Application.Run(new MainForm());
    }
}

4. 参考

おわりに

user_version は SQLite が用意している仕組みなので、別途マイグレーションの管理用テーブルを作らなくてもよくて楽です。SQLite の ALTER TABLE 周りは意図的に機能が限定されてそうなので、スキーマ変更を行う場合は、都度確認した方が良いです。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?