Switch startup DB upgrades to EF migrations
This commit is contained in:
4
FAQ.md
4
FAQ.md
@@ -26,6 +26,10 @@ Backend state is persisted via EF Core + SQLite.
|
|||||||
|
|
||||||
To start with a clean backend state, stop the app and remove the corresponding SQLite file.
|
To start with a clean backend state, stop the app and remove the corresponding SQLite file.
|
||||||
|
|
||||||
|
## Do I need to run manual DB migrations after updating the app?
|
||||||
|
|
||||||
|
Usually no. Startup now uses EF Core migration-based schema upgrades (`Database.Migrate`) and applies pending migrations automatically.
|
||||||
|
|
||||||
## Does the backend read SQLite on every API call?
|
## Does the backend read SQLite on every API call?
|
||||||
|
|
||||||
No. The backend loads state from SQLite once during startup into in-memory state and serves requests from memory. Successful state mutations are then written back to SQLite.
|
No. The backend loads state from SQLite once during startup into in-memory state and serves requests from memory. Successful state mutations are then written back to SQLite.
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ Backend state persistence:
|
|||||||
- EF Core with SQLite (`Microsoft.EntityFrameworkCore.Sqlite`)
|
- EF Core with SQLite (`Microsoft.EntityFrameworkCore.Sqlite`)
|
||||||
- Development DB: `RpgRoller/App_Data/rpgroller.development.db`
|
- Development DB: `RpgRoller/App_Data/rpgroller.development.db`
|
||||||
- Default DB: `RpgRoller/App_Data/rpgroller.db`
|
- Default DB: `RpgRoller/App_Data/rpgroller.db`
|
||||||
- Database schema is created automatically on startup (`EnsureCreated`)
|
- Database schema is created/upgraded automatically on startup via EF Core migrations (`Database.Migrate`)
|
||||||
- Runtime state is loaded once at startup into memory and written back to SQLite on successful state changes
|
- Runtime state is loaded once at startup into memory and written back to SQLite on successful state changes
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using RpgRoller.Hosting;
|
using RpgRoller.Hosting;
|
||||||
|
using RpgRoller.Data;
|
||||||
|
|
||||||
namespace RpgRoller.Tests;
|
namespace RpgRoller.Tests;
|
||||||
|
|
||||||
@@ -27,4 +31,99 @@ public sealed class HostingCoverageTests
|
|||||||
Assert.Contains(services, d => d.ServiceType == typeof(RpgRoller.Services.IGameService));
|
Assert.Contains(services, d => d.ServiceType == typeof(RpgRoller.Services.IGameService));
|
||||||
Assert.Contains(services, d => d.ServiceType == typeof(RpgRoller.Services.IDiceRoller));
|
Assert.Contains(services, d => d.ServiceType == typeof(RpgRoller.Services.IDiceRoller));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SqliteSchemaUpgrader_MigratesLegacySkillsSchema()
|
||||||
|
{
|
||||||
|
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-legacy-upgrade-{Guid.NewGuid():N}.db");
|
||||||
|
var connectionString = $"Data Source={dbPath}";
|
||||||
|
|
||||||
|
using (var connection = new SqliteConnection(connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText =
|
||||||
|
"""
|
||||||
|
CREATE TABLE "Users" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY,
|
||||||
|
"Username" TEXT NOT NULL,
|
||||||
|
"UsernameNormalized" TEXT NOT NULL,
|
||||||
|
"PasswordHash" TEXT NOT NULL,
|
||||||
|
"DisplayName" TEXT NOT NULL,
|
||||||
|
"ActiveCharacterId" TEXT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "Sessions" (
|
||||||
|
"Token" TEXT NOT NULL CONSTRAINT "PK_Sessions" PRIMARY KEY,
|
||||||
|
"UserId" TEXT NOT NULL,
|
||||||
|
"CreatedAtUtc" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "Campaigns" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Campaigns" PRIMARY KEY,
|
||||||
|
"GmUserId" TEXT NOT NULL,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Ruleset" TEXT NOT NULL,
|
||||||
|
"Version" INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "Characters" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Characters" PRIMARY KEY,
|
||||||
|
"OwnerUserId" TEXT NOT NULL,
|
||||||
|
"CampaignId" TEXT NOT NULL,
|
||||||
|
"Name" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "Skills" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Skills" PRIMARY KEY,
|
||||||
|
"CharacterId" TEXT NOT NULL,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"DiceRollDefinition" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "RollLogEntries" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_RollLogEntries" PRIMARY KEY,
|
||||||
|
"CampaignId" TEXT NOT NULL,
|
||||||
|
"CharacterId" TEXT NOT NULL,
|
||||||
|
"SkillId" TEXT NOT NULL,
|
||||||
|
"RollerUserId" TEXT NOT NULL,
|
||||||
|
"Visibility" TEXT NOT NULL,
|
||||||
|
"Result" INTEGER NOT NULL,
|
||||||
|
"Breakdown" TEXT NOT NULL,
|
||||||
|
"TimestampUtc" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
""";
|
||||||
|
_ = command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new DbContextOptionsBuilder<RpgRollerDbContext>()
|
||||||
|
.UseSqlite(connectionString)
|
||||||
|
.ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning))
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
using (var db = new RpgRollerDbContext(options))
|
||||||
|
{
|
||||||
|
SqliteSchemaUpgrader.ApplyPendingChanges(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var verifyConnection = new SqliteConnection(connectionString);
|
||||||
|
verifyConnection.Open();
|
||||||
|
|
||||||
|
using var tableInfoCommand = verifyConnection.CreateCommand();
|
||||||
|
tableInfoCommand.CommandText = "PRAGMA table_info('Skills');";
|
||||||
|
using var tableInfoReader = tableInfoCommand.ExecuteReader();
|
||||||
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
while (tableInfoReader.Read())
|
||||||
|
{
|
||||||
|
columns.Add(tableInfoReader.GetString(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Contains("WildDice", columns);
|
||||||
|
Assert.Contains("AllowFumble", columns);
|
||||||
|
|
||||||
|
using var historyCommand = verifyConnection.CreateCommand();
|
||||||
|
historyCommand.CommandText = "SELECT COUNT(*) FROM \"__EFMigrationsHistory\" WHERE \"MigrationId\" = '20260226084000_InitialSchema';";
|
||||||
|
var historyCount = Convert.ToInt32(historyCommand.ExecuteScalar());
|
||||||
|
Assert.Equal(1, historyCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Net;
|
|||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using Microsoft.AspNetCore.Mvc.Testing;
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using RpgRoller.Contracts;
|
using RpgRoller.Contracts;
|
||||||
@@ -32,7 +33,9 @@ public abstract class ApiTestBase : IClassFixture<WebApplicationFactory<Program>
|
|||||||
services.RemoveAll<RpgRollerDbContext>();
|
services.RemoveAll<RpgRollerDbContext>();
|
||||||
|
|
||||||
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-tests-{Guid.NewGuid():N}.db");
|
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-tests-{Guid.NewGuid():N}.db");
|
||||||
services.AddDbContextFactory<RpgRollerDbContext>(options => options.UseSqlite($"Data Source={dbPath}"));
|
services.AddDbContextFactory<RpgRollerDbContext>(options =>
|
||||||
|
options.UseSqlite($"Data Source={dbPath}")
|
||||||
|
.ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning)));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public static class ApplicationInitializationExtensions
|
|||||||
using var scope = app.Services.CreateScope();
|
using var scope = app.Services.CreateScope();
|
||||||
var dbFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<RpgRollerDbContext>>();
|
var dbFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<RpgRollerDbContext>>();
|
||||||
using var db = dbFactory.CreateDbContext();
|
using var db = dbFactory.CreateDbContext();
|
||||||
db.Database.EnsureCreated();
|
SqliteSchemaUpgrader.ApplyPendingChanges(db);
|
||||||
_ = scope.ServiceProvider.GetRequiredService<IGameService>();
|
_ = scope.ServiceProvider.GetRequiredService<IGameService>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using RpgRoller.Data;
|
using RpgRoller.Data;
|
||||||
using RpgRoller.Domain;
|
using RpgRoller.Domain;
|
||||||
using RpgRoller.Services;
|
using RpgRoller.Services;
|
||||||
@@ -18,7 +19,9 @@ public static class ServiceCollectionExtensions
|
|||||||
EnsureSqliteDataDirectory(sqliteConnectionString, environment.ContentRootPath);
|
EnsureSqliteDataDirectory(sqliteConnectionString, environment.ContentRootPath);
|
||||||
|
|
||||||
services.AddSingleton<IPasswordHasher<UserAccount>, PasswordHasher<UserAccount>>();
|
services.AddSingleton<IPasswordHasher<UserAccount>, PasswordHasher<UserAccount>>();
|
||||||
services.AddDbContextFactory<RpgRollerDbContext>(options => options.UseSqlite(sqliteConnectionString));
|
services.AddDbContextFactory<RpgRollerDbContext>(options =>
|
||||||
|
options.UseSqlite(sqliteConnectionString)
|
||||||
|
.ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning)));
|
||||||
services.AddSingleton<IDiceRoller, RandomDiceRoller>();
|
services.AddSingleton<IDiceRoller, RandomDiceRoller>();
|
||||||
services.AddSingleton<IGameService, GameService>();
|
services.AddSingleton<IGameService, GameService>();
|
||||||
|
|
||||||
|
|||||||
104
RpgRoller/Hosting/SqliteSchemaUpgrader.cs
Normal file
104
RpgRoller/Hosting/SqliteSchemaUpgrader.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using RpgRoller.Data;
|
||||||
|
|
||||||
|
namespace RpgRoller.Hosting;
|
||||||
|
|
||||||
|
public static class SqliteSchemaUpgrader
|
||||||
|
{
|
||||||
|
private const string InitialMigrationId = "20260226084000_InitialSchema";
|
||||||
|
private const string ProductVersion = "10.0.2";
|
||||||
|
|
||||||
|
public static void ApplyPendingChanges(RpgRollerDbContext db)
|
||||||
|
{
|
||||||
|
if (db.Database.IsSqlite())
|
||||||
|
{
|
||||||
|
EnsureLegacySchemaHistory(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureLegacySchemaHistory(RpgRollerDbContext db)
|
||||||
|
{
|
||||||
|
db.Database.OpenConnection();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 = InitialMigrationId;
|
||||||
|
insertHistoryCommand.Parameters.Add(migrationParameter);
|
||||||
|
|
||||||
|
var productVersionParameter = insertHistoryCommand.CreateParameter();
|
||||||
|
productVersionParameter.ParameterName = "$productVersion";
|
||||||
|
productVersionParameter.Value = ProductVersion;
|
||||||
|
insertHistoryCommand.Parameters.Add(productVersionParameter);
|
||||||
|
|
||||||
|
_ = insertHistoryCommand.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
db.Database.CloseConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
111
RpgRoller/Migrations/20260226084000_InitialSchema.cs
Normal file
111
RpgRoller/Migrations/20260226084000_InitialSchema.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using RpgRoller.Data;
|
||||||
|
|
||||||
|
namespace RpgRoller.Migrations;
|
||||||
|
|
||||||
|
[DbContext(typeof(RpgRollerDbContext))]
|
||||||
|
[Migration("20260226084000_InitialSchema")]
|
||||||
|
public sealed class InitialSchema : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Users" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY,
|
||||||
|
"Username" TEXT NOT NULL,
|
||||||
|
"UsernameNormalized" TEXT NOT NULL,
|
||||||
|
"PasswordHash" TEXT NOT NULL,
|
||||||
|
"DisplayName" TEXT NOT NULL,
|
||||||
|
"ActiveCharacterId" TEXT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Sessions" (
|
||||||
|
"Token" TEXT NOT NULL CONSTRAINT "PK_Sessions" PRIMARY KEY,
|
||||||
|
"UserId" TEXT NOT NULL,
|
||||||
|
"CreatedAtUtc" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Campaigns" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Campaigns" PRIMARY KEY,
|
||||||
|
"GmUserId" TEXT NOT NULL,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"Ruleset" TEXT NOT NULL,
|
||||||
|
"Version" INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Characters" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Characters" PRIMARY KEY,
|
||||||
|
"OwnerUserId" TEXT NOT NULL,
|
||||||
|
"CampaignId" TEXT NOT NULL,
|
||||||
|
"Name" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS "Skills" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_Skills" PRIMARY KEY,
|
||||||
|
"CharacterId" TEXT NOT NULL,
|
||||||
|
"Name" TEXT NOT NULL,
|
||||||
|
"DiceRollDefinition" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
ALTER TABLE "Skills" ADD COLUMN "WildDice" INTEGER NOT NULL DEFAULT 1;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
ALTER TABLE "Skills" ADD COLUMN "AllowFumble" INTEGER NOT NULL DEFAULT 1;
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS "RollLogEntries" (
|
||||||
|
"Id" TEXT NOT NULL CONSTRAINT "PK_RollLogEntries" PRIMARY KEY,
|
||||||
|
"CampaignId" TEXT NOT NULL,
|
||||||
|
"CharacterId" TEXT NOT NULL,
|
||||||
|
"SkillId" TEXT NOT NULL,
|
||||||
|
"RollerUserId" TEXT NOT NULL,
|
||||||
|
"Visibility" TEXT NOT NULL,
|
||||||
|
"Result" INTEGER NOT NULL,
|
||||||
|
"Breakdown" TEXT NOT NULL,
|
||||||
|
"TimestampUtc" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("""CREATE UNIQUE INDEX IF NOT EXISTS "IX_Users_UsernameNormalized" ON "Users" ("UsernameNormalized");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Sessions_UserId" ON "Sessions" ("UserId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Campaigns_GmUserId" ON "Campaigns" ("GmUserId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Characters_OwnerUserId" ON "Characters" ("OwnerUserId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Characters_CampaignId" ON "Characters" ("CampaignId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_Skills_CharacterId" ON "Skills" ("CharacterId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_RollLogEntries_CampaignId" ON "RollLogEntries" ("CampaignId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_RollLogEntries_RollerUserId" ON "RollLogEntries" ("RollerUserId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_RollLogEntries_SkillId" ON "RollLogEntries" ("SkillId");""");
|
||||||
|
migrationBuilder.Sql("""CREATE INDEX IF NOT EXISTS "IX_RollLogEntries_CharacterId" ON "RollLogEntries" ("CharacterId");""");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "RollLogEntries";""");
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Skills";""");
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Characters";""");
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Campaigns";""");
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Sessions";""");
|
||||||
|
migrationBuilder.Sql("""DROP TABLE IF EXISTS "Users";""");
|
||||||
|
}
|
||||||
|
}
|
||||||
78
RpgRoller/Migrations/RpgRollerDbContextModelSnapshot.cs
Normal file
78
RpgRoller/Migrations/RpgRollerDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using RpgRoller.Data;
|
||||||
|
using RpgRoller.Domain;
|
||||||
|
|
||||||
|
namespace RpgRoller.Migrations;
|
||||||
|
|
||||||
|
[DbContext(typeof(RpgRollerDbContext))]
|
||||||
|
internal sealed class RpgRollerDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.2");
|
||||||
|
|
||||||
|
modelBuilder.Entity<Campaign>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(x => x.Id);
|
||||||
|
entity.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
|
entity.Property(x => x.Ruleset).HasConversion<string>().IsRequired();
|
||||||
|
entity.Property(x => x.Version).IsRequired();
|
||||||
|
entity.HasIndex(x => x.GmUserId);
|
||||||
|
entity.ToTable("Campaigns");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Character>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(x => x.Id);
|
||||||
|
entity.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
|
entity.HasIndex(x => x.OwnerUserId);
|
||||||
|
entity.HasIndex(x => x.CampaignId);
|
||||||
|
entity.ToTable("Characters");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<RollLogEntry>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(x => x.Id);
|
||||||
|
entity.Property(x => x.Visibility).HasConversion<string>().IsRequired();
|
||||||
|
entity.Property(x => x.Breakdown).IsRequired().HasMaxLength(256);
|
||||||
|
entity.Property(x => x.TimestampUtc).IsRequired();
|
||||||
|
entity.HasIndex(x => x.CampaignId);
|
||||||
|
entity.HasIndex(x => x.RollerUserId);
|
||||||
|
entity.HasIndex(x => x.SkillId);
|
||||||
|
entity.HasIndex(x => x.CharacterId);
|
||||||
|
entity.ToTable("RollLogEntries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<Skill>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(x => x.Id);
|
||||||
|
entity.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||||
|
entity.Property(x => x.DiceRollDefinition).IsRequired().HasMaxLength(128);
|
||||||
|
entity.Property(x => x.WildDice).IsRequired();
|
||||||
|
entity.Property(x => x.AllowFumble).IsRequired();
|
||||||
|
entity.HasIndex(x => x.CharacterId);
|
||||||
|
entity.ToTable("Skills");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<UserAccount>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(x => x.Id);
|
||||||
|
entity.Property(x => x.Username).IsRequired().HasMaxLength(64);
|
||||||
|
entity.Property(x => x.UsernameNormalized).IsRequired().HasMaxLength(64);
|
||||||
|
entity.Property(x => x.PasswordHash).IsRequired();
|
||||||
|
entity.Property(x => x.DisplayName).IsRequired().HasMaxLength(128);
|
||||||
|
entity.HasIndex(x => x.UsernameNormalized).IsUnique();
|
||||||
|
entity.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<UserSession>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(x => x.Token);
|
||||||
|
entity.Property(x => x.Token).HasMaxLength(64);
|
||||||
|
entity.Property(x => x.CreatedAtUtc).IsRequired();
|
||||||
|
entity.HasIndex(x => x.UserId);
|
||||||
|
entity.ToTable("Sessions");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user