Skip to content

Commit 2271578

Browse files
slang25claude
andcommitted
Extract EF migrations into a dedicated worker project
Follows the .NET Aspire EF Core migrations guidance: a separate PocketDDD.Server.MigrationService Worker project applies migrations and seeds the placeholder EventDetail row, then stops itself. The AppHost gates the WebAPI on `WaitForCompletion(migrations)` so the API only starts after migrations complete. Removes the dev-only migrate-and-seed block previously embedded in WebAPI/Program.cs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 433a9a6 commit 2271578

8 files changed

Lines changed: 113 additions & 15 deletions

File tree

PocketDDD.AppHost/AppHost.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@
99
.WithReadStaticMappings()
1010
.WithWatchStaticMappings();
1111

12+
var migrations = builder.AddProject<Projects.PocketDDD_Server_MigrationService>("migrations")
13+
.WithReference(db)
14+
.WaitFor(db);
15+
1216
var api = builder.AddProject<Projects.PocketDDD_Server_WebAPI>("webapi")
1317
.WithEndpoint("https", e => { e.Port = 7081; e.IsProxied = false; })
1418
.WithReference(db)
15-
.WaitFor(db)
19+
.WaitForCompletion(migrations)
1620
.WithReference(sessionize)
1721
.WaitFor(sessionize)
1822
.WithEnvironment("Sessionize__BaseAddress", sessionize.GetEndpoint("http"));

PocketDDD.AppHost/PocketDDD.AppHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<ItemGroup>
2323
<ProjectReference Include="..\PocketDDD.BlazorClient\PocketDDD.BlazorClient\PocketDDD.BlazorClient.csproj" />
2424
<ProjectReference Include="..\PocketDDD.Server\PocketDDD.Server.WebAPI\PocketDDD.Server.WebAPI.csproj" />
25+
<ProjectReference Include="..\PocketDDD.Server\PocketDDD.Server.MigrationService\PocketDDD.Server.MigrationService.csproj" />
2526
</ItemGroup>
2627

2728
</Project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Worker">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<UserSecretsId>e6c3a5fa-0bb7-4a4f-b6d1-1cf68a5b7c44</UserSecretsId>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.7" />
12+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
13+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
14+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
15+
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.15.2" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="..\PocketDDD.Server.DB\PocketDDD.Server.DB.csproj" />
20+
<ProjectReference Include="..\PocketDDD.Server.Model\PocketDDD.Server.Model.csproj" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Hosting;
4+
using OpenTelemetry;
5+
using OpenTelemetry.Trace;
6+
using PocketDDD.Server.DB;
7+
using PocketDDD.Server.MigrationService;
8+
9+
var builder = Host.CreateApplicationBuilder(args);
10+
11+
builder.Services.AddOpenTelemetry()
12+
.WithTracing(tracing => tracing
13+
.AddSource(Worker.ActivitySourceName)
14+
.AddSqlClientInstrumentation())
15+
.UseOtlpExporter();
16+
17+
builder.Services.AddDbContext<PocketDDDContext>(options =>
18+
options.UseSqlServer("name=ConnectionStrings:PocketDDDContext"));
19+
20+
builder.Services.AddHostedService<Worker>();
21+
22+
var host = builder.Build();
23+
host.Run();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Diagnostics;
2+
using Microsoft.EntityFrameworkCore;
3+
using PocketDDD.Server.DB;
4+
using PocketDDD.Server.Model.DBModel;
5+
6+
namespace PocketDDD.Server.MigrationService;
7+
8+
public class Worker(
9+
IServiceProvider serviceProvider,
10+
IHostApplicationLifetime lifetime,
11+
ILogger<Worker> logger) : BackgroundService
12+
{
13+
public const string ActivitySourceName = "PocketDDD.Migrations";
14+
15+
private static readonly ActivitySource ActivitySource = new(ActivitySourceName);
16+
17+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
18+
{
19+
using var activity = ActivitySource.StartActivity("Migrating database", ActivityKind.Client);
20+
21+
try
22+
{
23+
using var scope = serviceProvider.CreateScope();
24+
var db = scope.ServiceProvider.GetRequiredService<PocketDDDContext>();
25+
26+
await RunMigrationAsync(db, stoppingToken);
27+
await SeedAsync(db, stoppingToken);
28+
}
29+
catch (Exception ex)
30+
{
31+
activity?.AddException(ex);
32+
logger.LogError(ex, "Database migration failed.");
33+
throw;
34+
}
35+
36+
lifetime.StopApplication();
37+
}
38+
39+
private static Task RunMigrationAsync(PocketDDDContext db, CancellationToken cancellationToken)
40+
{
41+
var strategy = db.Database.CreateExecutionStrategy();
42+
return strategy.ExecuteAsync(() => db.Database.MigrateAsync(cancellationToken));
43+
}
44+
45+
private static async Task SeedAsync(PocketDDDContext db, CancellationToken cancellationToken)
46+
{
47+
if (await db.EventDetail.AnyAsync(cancellationToken)) return;
48+
49+
db.EventDetail.Add(new EventDetail { SessionizeId = "dev-event", Version = 0 });
50+
await db.SaveChangesAsync(cancellationToken);
51+
}
52+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.Hosting.Lifetime": "Information"
6+
}
7+
}
8+
}

PocketDDD.Server/PocketDDD.Server.WebAPI/Program.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using OpenTelemetry.Metrics;
44
using OpenTelemetry.Trace;
55
using PocketDDD.Server.DB;
6-
using PocketDDD.Server.Model.DBModel;
76
using PocketDDD.Server.Services;
87
using PocketDDD.Server.WebAPI;
98
using PocketDDD.Server.WebAPI.Authentication;
@@ -71,19 +70,6 @@
7170

7271
var app = builder.Build();
7372

74-
if (app.Environment.IsDevelopment())
75-
{
76-
using var scope = app.Services.CreateScope();
77-
var db = scope.ServiceProvider.GetRequiredService<PocketDDDContext>();
78-
await db.Database.MigrateAsync();
79-
80-
if (!await db.EventDetail.AnyAsync())
81-
{
82-
db.EventDetail.Add(new EventDetail { SessionizeId = "dev-event", Version = 0 });
83-
await db.SaveChangesAsync();
84-
}
85-
}
86-
8773
// Configure the HTTP request pipeline.
8874
if (app.Environment.IsDevelopment())
8975
{

PocketDDD.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</Folder>
66
<Folder Name="/PocketDDD.Server/">
77
<Project Path="PocketDDD.Server/PocketDDD.Server.DB/PocketDDD.Server.DB.csproj" />
8+
<Project Path="PocketDDD.Server/PocketDDD.Server.MigrationService/PocketDDD.Server.MigrationService.csproj" />
89
<Project Path="PocketDDD.Server/PocketDDD.Server.Model/PocketDDD.Server.Model.csproj" />
910
<Project Path="PocketDDD.Server/PocketDDD.Server.Services/PocketDDD.Server.Services.csproj" />
1011
<Project Path="PocketDDD.Server/PocketDDD.Server.WebAPI/PocketDDD.Server.WebAPI.csproj" />

0 commit comments

Comments
 (0)