feat(dir): apply migrations on startup

This commit is contained in:
Collin Barrett 2024-05-31 16:43:26 -05:00
parent 93768e4571
commit cb4435f331
4 changed files with 59 additions and 24 deletions

View file

@ -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
/// <remarks>https://learn.microsoft.com/en-us/dotnet/aspire/database/ef-core-migrations#create-the-migration-service</remarks>
public class Worker(
IServiceProvider serviceProvider,
IHostApplicationLifetime hostApplicationLifetime,
IHostEnvironment hostEnvironment) : BackgroundService
{
private readonly ILogger<Worker> _logger;
private const string ActivitySourceName = "Migrations";
private static readonly ActivitySource ActivitySource = new(ActivitySourceName);
public Worker(ILogger<Worker> 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<QueryDbContext>();
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<IRelationalDatabaseCreator>();
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);
});
}
}

View file

@ -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<QueryDbContext>();
await db.Database.MigrateAsync();
}
}
internal static class SeedConfigurationExtensions
{
private static readonly JsonSerializerOptions CamelCaseJsonSerializerOptions =

View file

@ -11,6 +11,7 @@
<ItemGroup>
<ProjectReference Include="..\Directory\FilterLists.Directory.Api\FilterLists.Directory.Api.csproj"/>
<ProjectReference Include="..\Directory\FilterLists.Directory.Infrastructure.MigrationService\FilterLists.Directory.Infrastructure.MigrationService.csproj"/>
<ProjectReference Include="..\FilterLists.Web\FilterLists.Web.csproj"/>
</ItemGroup>

View file

@ -9,6 +9,10 @@
.PublishAsConnectionString() // customized for free tier, don't trust Aspire to provision db
.AddDatabase("directorydb");
builder.AddProject<FilterLists_Directory_Infrastructure_MigrationService>("directorymigrationservice")
.WithReference(directoryDb)
.WithReference(appInsights);
var directoryApi = builder.AddProject<FilterLists_Directory_Api>("directoryapi")
.WithReference(directoryDb)
.WithReference(appInsights);