using Microsoft.EntityFrameworkCore; using RpgRoller.Data; namespace RpgRoller.Hosting; public static class SqliteSchemaUpgrader { public static void ApplyPendingChanges(RpgRollerDbContext db) { if (db.Database.IsSqlite()) { db.Database.OpenConnection(); try { EnsureLegacySchemaHistory(db); EnsureSplitMigrationHistory(db); } finally { db.Database.CloseConnection(); } } db.Database.Migrate(); } private static void EnsureLegacySchemaHistory(RpgRollerDbContext db) { if (TableExists(db, "__EFMigrationsHistory")) return; if (!TableExists(db, "Skills")) return; if (!ColumnExists(db, "Skills", "WildDice") || !ColumnExists(db, "Skills", "AllowFumble")) return; using var createHistoryCommand = db.Database.GetDbConnection().CreateCommand(); createHistoryCommand.CommandText = """ CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY, "ProductVersion" TEXT NOT NULL ); """; _ = createHistoryCommand.ExecuteNonQuery(); InsertMigrationHistory(db, InitialMigrationId); } private static void EnsureSplitMigrationHistory(RpgRollerDbContext db) { if (!TableExists(db, "__EFMigrationsHistory")) return; if (!MigrationExists(db, CharactersCampaignDeletionMigrationId)) return; if (MigrationExists(db, AuthorizationRolesMigrationId)) return; if (!ColumnExists(db, "Users", "Roles")) return; InsertMigrationHistory(db, AuthorizationRolesMigrationId); } private static bool TableExists(RpgRollerDbContext db, string tableName) { using var command = db.Database.GetDbConnection().CreateCommand(); command.CommandText = "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$name LIMIT 1;"; var parameter = command.CreateParameter(); parameter.ParameterName = "$name"; parameter.Value = tableName; command.Parameters.Add(parameter); return command.ExecuteScalar() is not null; } private static bool ColumnExists(RpgRollerDbContext db, string tableName, string columnName) { using var command = db.Database.GetDbConnection().CreateCommand(); command.CommandText = $"PRAGMA table_info('{tableName}');"; using var reader = command.ExecuteReader(); while (reader.Read()) { var currentColumnName = reader.GetString(1); if (string.Equals(currentColumnName, columnName, StringComparison.OrdinalIgnoreCase)) return true; } return false; } private static bool MigrationExists(RpgRollerDbContext db, string migrationId) { using var command = db.Database.GetDbConnection().CreateCommand(); command.CommandText = """ SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = $migrationId LIMIT 1; """; var migrationParameter = command.CreateParameter(); migrationParameter.ParameterName = "$migrationId"; migrationParameter.Value = migrationId; command.Parameters.Add(migrationParameter); return command.ExecuteScalar() is not null; } private static void InsertMigrationHistory(RpgRollerDbContext db, string migrationId) { using var insertHistoryCommand = db.Database.GetDbConnection().CreateCommand(); insertHistoryCommand.CommandText = """ INSERT OR IGNORE INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ($migrationId, $productVersion); """; var migrationParameter = insertHistoryCommand.CreateParameter(); migrationParameter.ParameterName = "$migrationId"; migrationParameter.Value = migrationId; insertHistoryCommand.Parameters.Add(migrationParameter); var productVersionParameter = insertHistoryCommand.CreateParameter(); productVersionParameter.ParameterName = "$productVersion"; productVersionParameter.Value = ProductVersion; insertHistoryCommand.Parameters.Add(productVersionParameter); _ = insertHistoryCommand.ExecuteNonQuery(); } private const string InitialMigrationId = "20260226084000_InitialSchema"; private const string CharactersCampaignDeletionMigrationId = "20260226160859_AddAuthorizationRolesAndCampaignDeletion"; private const string AuthorizationRolesMigrationId = "20260226170000_AddAuthorizationRoles"; private const string ProductVersion = "10.0.2"; }