Skip to content

Commit 6ebf002

Browse files
fix: Database migration
1 parent 8d7e3a1 commit 6ebf002

2 files changed

Lines changed: 10 additions & 12 deletions

File tree

EssentialCSharp.Web/DatabaseMigrationService.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
using EssentialCSharp.Web.Data;
1+
using EssentialCSharp.Web.Data;
22
using Microsoft.EntityFrameworkCore;
33

44
namespace EssentialCSharp.Web;
55

66
/// <summary>
7-
/// Runs EF Core migrations synchronously in <see cref="StartAsync"/> so the schema
8-
/// is fully applied before the HTTP server begins accepting traffic. This prevents
9-
/// race conditions where a request touches the DataProtectionKeys (or Identity) tables
10-
/// before the migration that creates them has run.
7+
/// Runs EF Core database migrations on application startup. Must be registered before
8+
/// any hosted service that reads the database (e.g. <c>DataProtectionHostedService</c>),
9+
/// because <c>IHostedService</c> instances start in DI registration order.
1110
/// </summary>
1211
public class DatabaseMigrationService(IServiceScopeFactory services) : IHostedService
1312
{

EssentialCSharp.Web/Program.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,12 @@ private static void Main(string[] args)
122122

123123
builder.Services.AddDbContext<EssentialCSharpWebContext>(options => options.UseSqlServer(connectionString, sql => sql.EnableRetryOnFailure(5)));
124124

125-
// Data Protection — persist keys in SQL Server so they survive container restarts.
126-
// Local dev: SQL Server container with WithDataVolume keeps data between restarts.
127-
// Production (ACA via Terraform): Azure SQL connection string injected via env vars.
125+
// Must be registered before AddDataProtection(): hosted services start in registration
126+
// order, and DataProtectionHostedService reads DataProtectionKeys during startup.
127+
builder.Services.AddHostedService<DatabaseMigrationService>();
128+
129+
// Data Protection — persist keys to SQL Server so they survive container restarts.
128130
// SetApplicationName ensures the discriminator is stable across container hostname changes.
129-
// Production: if DataProtection:AzureKeyVaultKeyUri is set (Terraform injects it as
130-
// DataProtection__AzureKeyVaultKeyUri env var), keys are wrapped with Key Vault.
131-
// Requires: RSA key in Key Vault + managed identity with Key Vault Crypto User role.
132131
var dpBuilder = builder.Services.AddDataProtection()
133132
.SetApplicationName("EssentialCSharpWeb")
134133
.PersistKeysToDbContext<EssentialCSharpWebContext>();
@@ -238,7 +237,6 @@ private static void Main(string[] args)
238237
builder.Services.AddSingleton<ISiteMappingService, SiteMappingService>();
239238
builder.Services.AddSingleton<IRouteConfigurationService, RouteConfigurationService>();
240239
builder.Services.AddSingleton<IListingSourceCodeService, ListingSourceCodeService>();
241-
builder.Services.AddHostedService<DatabaseMigrationService>();
242240
builder.Services.AddScoped<IReferralService, ReferralService>();
243241

244242
// Add AI Chat services
@@ -359,6 +357,7 @@ await context.HttpContext.Response.WriteAsync(
359357
loggerFactory.Dispose();
360358

361359
WebApplication app = builder.Build();
360+
362361
// Configure the HTTP request pipeline.
363362
if (!app.Environment.IsDevelopment())
364363
{

0 commit comments

Comments
 (0)