From cb4435f3310088b9dd9e70d77a9538f6d5014252 Mon Sep 17 00:00:00 2001 From: Collin Barrett Date: Fri, 31 May 2024 16:43:26 -0500 Subject: [PATCH] =?UTF-8?q?feat(dir):=20=E2=9C=A8=20apply=20migrations=20o?= =?UTF-8?q?n=20startup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Worker.cs | 64 ++++++++++++++++--- .../Persistence/SeedExtensions.cs | 14 ---- .../FilterLists.AppHost.csproj | 1 + services/FilterLists.AppHost/Program.cs | 4 ++ 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Worker.cs b/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Worker.cs index d1008a3dc..36fcecfc2 100644 --- a/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Worker.cs +++ b/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Worker.cs @@ -1,21 +1,65 @@ +using System.Diagnostics; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using OpenTelemetry.Trace; + namespace FilterLists.Directory.Infrastructure.MigrationService; -public class Worker : BackgroundService +/// https://learn.microsoft.com/en-us/dotnet/aspire/database/ef-core-migrations#create-the-migration-service +public class Worker( + IServiceProvider serviceProvider, + IHostApplicationLifetime hostApplicationLifetime, + IHostEnvironment hostEnvironment) : BackgroundService { - private readonly ILogger _logger; + private const string ActivitySourceName = "Migrations"; + private static readonly ActivitySource ActivitySource = new(ActivitySourceName); - public Worker(ILogger logger) + protected override async Task ExecuteAsync(CancellationToken cancellationToken) { - _logger = logger; + // allow SQL Server container time to start + await Task.Delay(3000, cancellationToken); + + // ReSharper disable once ExplicitCallerInfoArgument + using var activity = ActivitySource.StartActivity("Migrating database", ActivityKind.Client); + + try + { + using var scope = serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + if (hostEnvironment.IsDevelopment()) await EnsureDatabaseAsync(dbContext, cancellationToken); + await RunMigrationAsync(dbContext, cancellationToken); + } + catch (Exception ex) + { + activity?.RecordException(ex); + throw; + } + + hostApplicationLifetime.StopApplication(); } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + private static async Task EnsureDatabaseAsync(QueryDbContext dbContext, CancellationToken cancellationToken) { - while (!stoppingToken.IsCancellationRequested) + var dbCreator = dbContext.GetService(); + + var strategy = dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => { - if (_logger.IsEnabled(LogLevel.Information)) - _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - await Task.Delay(1000, stoppingToken); - } + if (!await dbCreator.ExistsAsync(cancellationToken)) await dbCreator.CreateAsync(cancellationToken); + }); + } + + private static async Task RunMigrationAsync(QueryDbContext dbContext, CancellationToken cancellationToken) + { + var strategy = dbContext.Database.CreateExecutionStrategy(); + await strategy.ExecuteAsync(async () => + { + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); + await dbContext.Database.MigrateAsync(cancellationToken); + await transaction.CommitAsync(cancellationToken); + }); } } \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs index 3d1c08a25..edc589dfd 100644 --- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs +++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/SeedExtensions.cs @@ -1,22 +1,8 @@ using System.Text.Json; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; namespace FilterLists.Directory.Infrastructure.Persistence; -public static class SeedExtension -{ - public static async Task MigrateAsync(this IHost host) - { - using var scope = host.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - await db.Database.MigrateAsync(); - } -} - internal static class SeedConfigurationExtensions { private static readonly JsonSerializerOptions CamelCaseJsonSerializerOptions = diff --git a/services/FilterLists.AppHost/FilterLists.AppHost.csproj b/services/FilterLists.AppHost/FilterLists.AppHost.csproj index 9ef25c2e6..ddcb87976 100644 --- a/services/FilterLists.AppHost/FilterLists.AppHost.csproj +++ b/services/FilterLists.AppHost/FilterLists.AppHost.csproj @@ -11,6 +11,7 @@ + diff --git a/services/FilterLists.AppHost/Program.cs b/services/FilterLists.AppHost/Program.cs index 6b0b9b1ed..96ab2060d 100644 --- a/services/FilterLists.AppHost/Program.cs +++ b/services/FilterLists.AppHost/Program.cs @@ -9,6 +9,10 @@ .PublishAsConnectionString() // customized for free tier, don't trust Aspire to provision db .AddDatabase("directorydb"); +builder.AddProject("directorymigrationservice") + .WithReference(directoryDb) + .WithReference(appInsights); + var directoryApi = builder.AddProject("directoryapi") .WithReference(directoryDb) .WithReference(appInsights);