diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md index 9aa057d9f..bc1982b52 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md @@ -51,4 +51,10 @@ Once you've configured your connection strings via `appsettings.secrets.json` (o dotnet run ``` +Migration history flattening is disabled by default. To reconcile databases that still +contain migration ids from the removed migration set, run the migrator once with +`Database__FlattenMigrations=true` (or set `Database:FlattenMigrations` to `true` in +`appsettings.secrets.json`). Do not leave this enabled for normal migration runs: it +deletes migration history rows that were added after the flattened `Initial` migration. + Or run it from Visual Studio by setting `Unity.GrantManager.DbMigrator` as the startup project. diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json index 325101ebd..1e3abd26c 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json @@ -4,6 +4,9 @@ "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;", "Onboarding": "Host=localhost;port=5432;Database=Onboarding;Username=postgres;" }, + "Database": { + "FlattenMigrations": false + }, "StringEncryption": { "DefaultPassPhrase": "g2IuZx7PwXDvCmlW" }, diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs index 0b68fd752..290e4d9a6 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.EntityFrameworkCore/EntityFrameworkCore/EntityFrameworkCoreGrantManagerDbSchemaMigrator.cs @@ -20,6 +20,7 @@ namespace Unity.GrantManager.EntityFrameworkCore; public class EntityFrameworkCoreGrantManagerDbSchemaMigrator( IServiceProvider serviceProvider, IStringEncryptionService encryptionService, + IConfiguration configuration, ILogger logger) : IGrantManagerDbSchemaMigrator, ITransientDependency { @@ -30,13 +31,10 @@ public class EntityFrameworkCoreGrantManagerDbSchemaMigrator( * "Initial" and would make Database.MigrateAsync() below try to re-run Initial's * CreateTable operations against a schema that already has them. * - * ReconcileMigrationHistoryAsync resets history to just the Initial row *before* - * MigrateAsync() is called, so EF sees it as already applied and skips it. Brand - * new databases (including newly provisioned tenants) have an empty or nonexistent - * history table at this point, so the reconciliation is a no-op and MigrateAsync() - * runs Initial for real to build the schema. Safe to run unconditionally on every - * migrator invocation, forever - after the first run per database, history only - * ever contains the Initial row so the guard clause never fires again. + * ReconcileMigrationHistoryAsync resets history to just the Initial row before + * MigrateAsync() is called, so EF sees it as already applied and skips it. This is + * an explicit one-time operation because running it during normal migration startup + * would also remove legitimate migrations added after the flattening. */ private const string HostInitialMigrationId = "20260722193713_Initial"; private const string TenantInitialMigrationId = "20260721203242_Initial"; @@ -44,6 +42,7 @@ public class EntityFrameworkCoreGrantManagerDbSchemaMigrator( private readonly IServiceProvider _serviceProvider = serviceProvider; private readonly IStringEncryptionService _encryptionService = encryptionService; + private readonly bool _flattenMigrations = configuration.GetValue("Database:FlattenMigrations"); private readonly ILogger _logger = logger; public async Task MigrateAsync(Tenant? tenant) @@ -104,7 +103,10 @@ public async Task MigrateAsync(Tenant? tenant) await tenantDb.ExecuteSqlRawAsync( tenantDb.GetService().GetCreateIfNotExistsScript()); - await ReconcileMigrationHistoryAsync(tenantDb, TenantInitialMigrationId); + if (_flattenMigrations) + { + await ReconcileMigrationHistoryAsync(tenantDb, TenantInitialMigrationId); + } // Run migrations as admin against the tenant database await MigrateAndLogAsync(tenantDb, $"tenant:{tenant.Name}"); @@ -162,7 +164,10 @@ the correct one. */ await hostDb.ExecuteSqlRawAsync( hostDb.GetService().GetCreateIfNotExistsScript()); - await ReconcileMigrationHistoryAsync(hostDb, HostInitialMigrationId); + if (_flattenMigrations) + { + await ReconcileMigrationHistoryAsync(hostDb, HostInitialMigrationId); + } await MigrateAndLogAsync(hostDb, "host"); }