80 lines
3.3 KiB
C#
80 lines
3.3 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Npgsql;
|
|
|
|
namespace FinlyticCore.Database;
|
|
|
|
/// <summary>
|
|
/// Utility for auto-bootstrapping PostgreSQL databases in a multi-service architecture.
|
|
/// Ensures the target catalog database exists prior to EF Core connection and migration execution.
|
|
/// </summary>
|
|
public static class DatabaseBootstrapper
|
|
{
|
|
/// <summary>
|
|
/// Checks if the target PostgreSQL database exists. If not, connects to the default administrative
|
|
/// database ('postgres') and executes CREATE DATABASE so that EF Core migrations can succeed.
|
|
/// </summary>
|
|
public static async Task EnsureDatabaseCreatedAsync(
|
|
string connectionString,
|
|
ILogger? logger = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(connectionString)) return;
|
|
|
|
try
|
|
{
|
|
var builder = new NpgsqlConnectionStringBuilder(connectionString);
|
|
string targetDb = builder.Database ?? string.Empty;
|
|
|
|
if (string.IsNullOrWhiteSpace(targetDb) || string.Equals(targetDb, "postgres", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Temporarily connect to the default 'postgres' database to query pg_database
|
|
builder.Database = "postgres";
|
|
string adminConnStr = builder.ConnectionString;
|
|
|
|
await using var conn = new NpgsqlConnection(adminConnStr);
|
|
await conn.OpenAsync(cancellationToken);
|
|
|
|
await using var checkCmd = new NpgsqlCommand(
|
|
"SELECT 1 FROM pg_database WHERE datname = @dbname;", conn);
|
|
checkCmd.Parameters.AddWithValue("dbname", targetDb);
|
|
var exists = await checkCmd.ExecuteScalarAsync(cancellationToken);
|
|
|
|
if (exists == null || exists == DBNull.Value)
|
|
{
|
|
logger?.LogInformation("[DatabaseBootstrapper] Database '{TargetDb}' does not exist on PostgreSQL host. Creating it automatically...", targetDb);
|
|
|
|
// CREATE DATABASE cannot be executed as a parameterized identifier
|
|
await using var createCmd = new NpgsqlCommand(
|
|
$"CREATE DATABASE \"{targetDb.Replace("\"", "\"\"")}\";", conn);
|
|
await createCmd.ExecuteNonQueryAsync(cancellationToken);
|
|
|
|
logger?.LogInformation("[DatabaseBootstrapper] Successfully created database '{TargetDb}'.", targetDb);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger?.LogWarning(ex, "[DatabaseBootstrapper] Auto-creation check failed or skipped for connection. Continuing with migration.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combines catalog database auto-creation and EF Core Migration execution in a single call.
|
|
/// </summary>
|
|
public static async Task MigrateWithBootstrapAsync<TContext>(
|
|
this TContext context,
|
|
string connectionString,
|
|
ILogger? logger = null,
|
|
CancellationToken cancellationToken = default) where TContext : DbContext
|
|
{
|
|
await EnsureDatabaseCreatedAsync(connectionString, logger, cancellationToken);
|
|
await context.Database.MigrateAsync(cancellationToken);
|
|
}
|
|
}
|