Split SQLite rebuild migration from roles change

This commit is contained in:
2026-04-04 20:44:26 +02:00
parent 8c413a8ded
commit a5f8421aa8
7 changed files with 513 additions and 61 deletions

View File

@@ -8,56 +8,60 @@ public static class SqliteSchemaUpgrader
public static void ApplyPendingChanges(RpgRollerDbContext db)
{
if (db.Database.IsSqlite())
EnsureLegacySchemaHistory(db);
{
db.Database.OpenConnection();
try
{
EnsureLegacySchemaHistory(db);
EnsureSplitMigrationHistory(db);
}
finally
{
db.Database.CloseConnection();
}
}
db.Database.Migrate();
}
private static void EnsureLegacySchemaHistory(RpgRollerDbContext db)
{
db.Database.OpenConnection();
try
{
if (TableExists(db, "__EFMigrationsHistory"))
return;
if (TableExists(db, "__EFMigrationsHistory"))
return;
if (!TableExists(db, "Skills"))
return;
if (!TableExists(db, "Skills"))
return;
if (!ColumnExists(db, "Skills", "WildDice") || !ColumnExists(db, "Skills", "AllowFumble"))
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();
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();
using var insertHistoryCommand = db.Database.GetDbConnection().CreateCommand();
insertHistoryCommand.CommandText = """
INSERT OR IGNORE INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
VALUES ($migrationId, $productVersion);
""";
InsertMigrationHistory(db, InitialMigrationId);
}
var migrationParameter = insertHistoryCommand.CreateParameter();
migrationParameter.ParameterName = "$migrationId";
migrationParameter.Value = InitialMigrationId;
insertHistoryCommand.Parameters.Add(migrationParameter);
private static void EnsureSplitMigrationHistory(RpgRollerDbContext db)
{
if (!TableExists(db, "__EFMigrationsHistory"))
return;
var productVersionParameter = insertHistoryCommand.CreateParameter();
productVersionParameter.ParameterName = "$productVersion";
productVersionParameter.Value = ProductVersion;
insertHistoryCommand.Parameters.Add(productVersionParameter);
if (!MigrationExists(db, CharactersCampaignDeletionMigrationId))
return;
_ = insertHistoryCommand.ExecuteNonQuery();
}
finally
{
db.Database.CloseConnection();
}
if (MigrationExists(db, AuthorizationRolesMigrationId))
return;
if (!ColumnExists(db, "Users", "Roles"))
return;
InsertMigrationHistory(db, AuthorizationRolesMigrationId);
}
private static bool TableExists(RpgRollerDbContext db, string tableName)
@@ -87,6 +91,46 @@ public static class SqliteSchemaUpgrader
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";
}
}

View File

@@ -211,11 +211,6 @@ namespace RpgRoller.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Roles")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(64)

View File

@@ -11,14 +11,6 @@ namespace RpgRoller.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Roles",
table: "Users",
type: "TEXT",
maxLength: 256,
nullable: false,
defaultValue: "admin");
migrationBuilder.AlterColumn<Guid>(
name: "CampaignId",
table: "Characters",
@@ -31,10 +23,6 @@ namespace RpgRoller.Migrations
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Roles",
table: "Users");
migrationBuilder.AlterColumn<Guid>(
name: "CampaignId",
table: "Characters",

View File

@@ -0,0 +1,258 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RpgRoller.Data;
#nullable disable
namespace RpgRoller.Migrations
{
[DbContext(typeof(RpgRollerDbContext))]
[Migration("20260226170000_AddAuthorizationRoles")]
partial class AddAuthorizationRoles
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.2");
modelBuilder.Entity("RpgRoller.Domain.Campaign", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("GmUserId")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Ruleset")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("Version")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("GmUserId");
b.ToTable("Campaigns");
});
modelBuilder.Entity("RpgRoller.Domain.Character", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("CampaignId")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<Guid>("OwnerUserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CampaignId");
b.HasIndex("OwnerUserId");
b.ToTable("Characters");
});
modelBuilder.Entity("RpgRoller.Domain.RollLogEntry", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Breakdown")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<Guid>("CampaignId")
.HasColumnType("TEXT");
b.Property<Guid>("CharacterId")
.HasColumnType("TEXT");
b.Property<string>("Dice")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Result")
.HasColumnType("INTEGER");
b.Property<Guid>("RollerUserId")
.HasColumnType("TEXT");
b.Property<Guid>("SkillId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("TimestampUtc")
.HasColumnType("TEXT");
b.Property<string>("Visibility")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CampaignId");
b.HasIndex("CharacterId");
b.HasIndex("RollerUserId");
b.HasIndex("SkillId");
b.ToTable("RollLogEntries");
});
modelBuilder.Entity("RpgRoller.Domain.Skill", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("AllowFumble")
.HasColumnType("INTEGER");
b.Property<Guid>("CharacterId")
.HasColumnType("TEXT");
b.Property<string>("DiceRollDefinition")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<Guid?>("SkillGroupId")
.HasColumnType("TEXT");
b.Property<int>("WildDice")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CharacterId");
b.HasIndex("SkillGroupId");
b.ToTable("Skills");
});
modelBuilder.Entity("RpgRoller.Domain.SkillGroup", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("AllowFumble")
.HasColumnType("INTEGER");
b.Property<Guid>("CharacterId")
.HasColumnType("TEXT");
b.Property<string>("DiceRollDefinition")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<int>("WildDice")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CharacterId");
b.ToTable("SkillGroups");
});
modelBuilder.Entity("RpgRoller.Domain.UserAccount", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("ActiveCharacterId")
.HasColumnType("TEXT");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Roles")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("UsernameNormalized")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UsernameNormalized")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("RpgRoller.Domain.UserSession", b =>
{
b.Property<string>("Token")
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CreatedAtUtc")
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Token");
b.HasIndex("UserId");
b.ToTable("Sessions");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RpgRoller.Migrations
{
/// <inheritdoc />
public partial class AddAuthorizationRoles : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Roles",
table: "Users",
type: "TEXT",
maxLength: 256,
nullable: false,
defaultValue: "admin");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Roles",
table: "Users");
}
}
}