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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions