Skip to content

prevents “database does not exist" #1138

@github4mathews

Description

@github4mathews

Fix applied:
• Added hosted service PostgresDatabaseCreatorHostedService to ensure the fullstackhero database and hangfire schema exist (prevents “database does not exist” and missing hangfire.schema errors).
• Updated Infrastructure startup (ConfigureFshFramework) to register this hosted service.
• Created PostgresDatabaseCreatorHostedService which:
• Connects to postgres admin DB.
• Creates target DB if absent.
• Ensures hangfire schema exists.
Build succeeded.
Next steps for you:

  1. Remove any old conflicting Postgres container volume only if you want a clean slate (optional).
  2. Re-run Aspire host (F5). The DB should now exist before EF/Hangfire usage.
  3. Add missing migrations for contexts reporting “pending changes” (run dotnet ef migrations add ... in migrations project) to eliminate those warnings.

using System.Reflection;
using Asp.Versioning.Conventions;
using FluentValidation;
using FSH.Framework.Core;
using FSH.Framework.Core.Origin;
using FSH.Framework.Infrastructure.Auth;
using FSH.Framework.Infrastructure.Auth.Jwt;
using FSH.Framework.Infrastructure.Behaviours;
using FSH.Framework.Infrastructure.Caching;
using FSH.Framework.Infrastructure.Cors;
using FSH.Framework.Infrastructure.Exceptions;
using FSH.Framework.Infrastructure.Identity;
using FSH.Framework.Infrastructure.Jobs;
using FSH.Framework.Infrastructure.Logging.Serilog;
using FSH.Framework.Infrastructure.Mail;
using FSH.Framework.Infrastructure.OpenApi;
using FSH.Framework.Infrastructure.Persistence;
using FSH.Framework.Infrastructure.RateLimit;
using FSH.Framework.Infrastructure.SecurityHeaders;
using FSH.Framework.Infrastructure.Storage.Files;
using FSH.Framework.Infrastructure.Tenant;
using FSH.Framework.Infrastructure.Tenant.Endpoints;
using FSH.Starter.Aspire.ServiceDefaults;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;

namespace FSH.Framework.Infrastructure;

public static class Extensions
{
public static WebApplicationBuilder ConfigureFshFramework(this WebApplicationBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);
builder.AddServiceDefaults();
builder.ConfigureSerilog();
builder.ConfigureDatabase();
builder.Services.ConfigureMultitenancy();
builder.Services.ConfigureIdentity();
builder.Services.AddCorsPolicy(builder.Configuration);
builder.Services.ConfigureFileStorage();
builder.Services.ConfigureJwtAuth();
builder.Services.ConfigureOpenApi();
builder.Services.ConfigureJobs(builder.Configuration);
builder.Services.ConfigureMailing();
builder.Services.ConfigureCaching(builder.Configuration);
builder.Services.AddExceptionHandler();
builder.Services.AddProblemDetails();
builder.Services.AddHealthChecks();
builder.Services.AddOptions().BindConfiguration(nameof(OriginOptions));

    // Ensure Postgres database & hangfire schema are created before anything else tries to connect.
    builder.Services.AddHostedService<PostgresDatabaseCreatorHostedService>();

    // Define module assemblies
    var assemblies = new Assembly[]
    {
        typeof(FshCore).Assembly,
        typeof(FshInfrastructure).Assembly
    };

    // Register validators
    builder.Services.AddValidatorsFromAssemblies(assemblies);

    // Register MediatR
    builder.Services.AddMediatR(cfg =>
    {
        cfg.RegisterServicesFromAssemblies(assemblies);
        cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
    });

    builder.Services.ConfigureRateLimit(builder.Configuration);
    builder.Services.ConfigureSecurityHeaders(builder.Configuration);

    return builder;
}

public static WebApplication UseFshFramework(this WebApplication app)
{
    app.MapDefaultEndpoints();
    app.UseRateLimit();
    app.UseSecurityHeaders();
    app.UseMultitenancy();
    app.UseExceptionHandler();
    app.UseCorsPolicy();
    app.UseOpenApi();
    app.UseJobDashboard(app.Configuration);
    app.UseRouting();
    app.UseStaticFiles();
    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "assets")),
        RequestPath = new PathString("/assets")
    });
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapTenantEndpoints();
    app.MapIdentityEndpoints();

    // Current user middleware
    app.UseMiddleware<CurrentUserMiddleware>();

    // Register API versions
    var versions = app.NewApiVersionSet()
                .HasApiVersion(1)
                .HasApiVersion(2)
                .ReportApiVersions()
                .Build();

    // Map versioned endpoint
    app.MapGroup("api/v{version:apiVersion}").WithApiVersionSet(versions);

    return app;
}

}

=================================================================

using FSH.Framework.Core.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Npgsql;

namespace FSH.Framework.Infrastructure.Persistence;

///


/// Ensures the configured Postgres database (and hangfire schema) exist before the rest of the app starts using connections.
/// This mitigates race conditions where EF contexts / Hangfire attempt to connect to a DB that has not yet been created by Aspire.
///

internal sealed class PostgresDatabaseCreatorHostedService(
IServiceProvider serviceProvider,
IOptions dbOptions,
ILogger logger) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
var options = dbOptions.Value;
if (!string.Equals(options.Provider, DbProviders.PostgreSQL, StringComparison.OrdinalIgnoreCase))
{
return; // only for Postgres.
}

    try
    {
        var csb = new NpgsqlConnectionStringBuilder(options.ConnectionString);
        var databaseName = csb.Database;
        var adminConnectionString = new NpgsqlConnectionStringBuilder(options.ConnectionString)
        {
            Database = "postgres"
        }.ToString();

        await using (var admin = new NpgsqlConnection(adminConnectionString))
        {
            await admin.OpenAsync(cancellationToken);
            // Create database if not exists
            await using (var cmd = new NpgsqlCommand($"SELECT 1 FROM pg_database WHERE datname = @db", admin))
            {
                cmd.Parameters.AddWithValue("db", databaseName);
                var exists = await cmd.ExecuteScalarAsync(cancellationToken) is not null;
                if (!exists)
                {
                    await using var createCmd = new NpgsqlCommand($"CREATE DATABASE \"{databaseName}\"", admin);
                    await createCmd.ExecuteNonQueryAsync(cancellationToken);
                    logger.LogInformation("Created PostgreSQL database {Database}", databaseName);
                }
            }
        }

        // Ensure hangfire schema exists inside target DB
        await using (var conn = new NpgsqlConnection(options.ConnectionString))
        {
            await conn.OpenAsync(cancellationToken);
            await using var cmd = new NpgsqlCommand("CREATE SCHEMA IF NOT EXISTS hangfire", conn);
            await cmd.ExecuteNonQueryAsync(cancellationToken);
        }
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Failed ensuring PostgreSQL database / schema existence");
    }
}

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions