mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Update database handling code (#247)
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Connection;
|
||||
|
||||
public static class ConfigurationHelpers
|
||||
{
|
||||
public static string GetKeyOrThrow(IConfiguration configuration, string key) =>
|
||||
configuration[key]
|
||||
?? throw new InvalidOperationException($"The {key} environment variable is not set.");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Connection</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.79" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Types" Version="160.1000.6" />
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||
Version="9.0.0"
|
||||
/>
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
|
||||
Version="10.0.0"
|
||||
/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Data.Common;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Default <see cref="DefaultSqlConnectionFactory" /> implementation that creates SQL Server connections,
|
||||
/// resolving the connection string from environment variables or application configuration.
|
||||
/// </summary>
|
||||
public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The resolved SQL Server connection string, preferring (in order): the <c>DB_CONNECTION_STRING</c>
|
||||
/// configuration value, and a connection string built from individual <c>DB_*</c> configuration values.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when no connection string can be resolved from any source.</exception>
|
||||
public string ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
string? fullConnectionString = configuration["DB_CONNECTION_STRING"];
|
||||
if (!string.IsNullOrEmpty(fullConnectionString))
|
||||
return fullConnectionString;
|
||||
|
||||
// Try to build from individual configuration values (preferred method for Docker)
|
||||
if (
|
||||
SqlConnectionStringHelper.TryGetStandardDbConnectionString(
|
||||
configuration,
|
||||
out var connectionString
|
||||
)
|
||||
)
|
||||
return connectionString;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Database connection string not configured. Set DB_CONNECTION_STRING or DB_SERVER, DB_NAME, DB_USER, DB_PASSWORD env vars."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SqlConnection CreateConnection()
|
||||
{
|
||||
return new SqlConnection(ConnectionString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Database.Connection.DependencyInjection;
|
||||
|
||||
/// <summary>Extension methods for registering database connection services with dependency injection.</summary>
|
||||
public static class DatabaseConnectionServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>Registers <see cref="DefaultSqlConnectionFactory" /> as a singleton <see cref="ISqlConnectionFactory" />.</summary>
|
||||
/// <returns>The same <paramref name="services" /> instance, for chaining.</returns>
|
||||
public static IServiceCollection AddDatabaseConnection(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Database.Connection;
|
||||
|
||||
public interface ISqlConnectionFactory
|
||||
{
|
||||
public string ConnectionString { get; }
|
||||
|
||||
public SqlConnection CreateConnection();
|
||||
}
|
||||
21
web/backend/Database/Database.Connection/Repository.cs
Normal file
21
web/backend/Database/Database.Connection/Repository.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Database.Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for Dapper-based repositories, providing shared connection creation for derived
|
||||
/// repository implementations.
|
||||
/// </summary>
|
||||
public abstract class DapperRepository(ISqlConnectionFactory connectionFactory)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates and opens a new database connection using the configured <see cref="ISqlConnectionFactory" />.
|
||||
/// </summary>
|
||||
/// <exception cref="DbException">Thrown when the connection cannot be opened.</exception>
|
||||
protected async Task<DbConnection> CreateConnection()
|
||||
{
|
||||
DbConnection connection = connectionFactory.CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Connection;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for building SQL Server connection strings from configuration
|
||||
/// (backed by the <c>DB_*</c> environment variables).
|
||||
/// </summary>
|
||||
public static class SqlConnectionStringHelper
|
||||
{
|
||||
private static SqlConnectionStringBuilder MakeConfiguredConnectionBuilder(
|
||||
IConfiguration configuration
|
||||
) =>
|
||||
new()
|
||||
{
|
||||
DataSource = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_SERVER"),
|
||||
UserID = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_USER"),
|
||||
Password = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_PASSWORD"),
|
||||
TrustServerCertificate = ResolveTrustServerCertificate(configuration),
|
||||
Encrypt = true,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Builds a SQL Server connection string from configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns <c>false</c> if DB_SERVER, DB_NAME, DB_USER, or DB_PASSWORD is unset.
|
||||
/// </remarks>
|
||||
/// <param name="configuration">The configuration to read the <c>DB_*</c> values from.</param>
|
||||
/// <param name="connectionString">The resolved connection string, if successful.</param>
|
||||
public static bool TryGetStandardDbConnectionString(
|
||||
IConfiguration configuration,
|
||||
out string connectionString
|
||||
)
|
||||
{
|
||||
connectionString = string.Empty;
|
||||
try
|
||||
{
|
||||
SqlConnectionStringBuilder builder = MakeConfiguredConnectionBuilder(configuration);
|
||||
builder.InitialCatalog = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_NAME");
|
||||
connectionString = builder.ConnectionString;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetMasterConnectionString(
|
||||
IConfiguration configuration,
|
||||
out string connectionString
|
||||
)
|
||||
{
|
||||
connectionString = string.Empty;
|
||||
try
|
||||
{
|
||||
var builder = MakeConfiguredConnectionBuilder(configuration);
|
||||
builder.InitialCatalog = "master";
|
||||
|
||||
connectionString = builder.ConnectionString;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetDatabaseNameFromConnectionString(
|
||||
string connectionString,
|
||||
out string databaseName
|
||||
)
|
||||
{
|
||||
databaseName = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var builder = new SqlConnectionStringBuilder(connectionString);
|
||||
databaseName = builder.InitialCatalog;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the <c>DB_TRUST_SERVER_CERTIFICATE</c> configuration value, defaulting to
|
||||
/// <c>true</c> when it is unset or not a valid boolean.
|
||||
/// </summary>
|
||||
private static bool ResolveTrustServerCertificate(IConfiguration configuration)
|
||||
{
|
||||
return !bool.TryParse(
|
||||
configuration["DB_TRUST_SERVER_CERTIFICATE"],
|
||||
out bool trustServerCertificate
|
||||
) || trustServerCertificate;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
||||
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="scripts/**/*.sql" />
|
||||
|
||||
113
web/backend/Database/Database.Migrations/DbMigrator.cs
Normal file
113
web/backend/Database/Database.Migrations/DbMigrator.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Reflection;
|
||||
using Dapper;
|
||||
using Database.Connection;
|
||||
using DbUp;
|
||||
using DbUp.Engine;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Database.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Owns the configuration and state for a single database migration run,
|
||||
/// and performs the drop/create/deploy steps against SQL Server.
|
||||
/// </summary>
|
||||
internal sealed class DbMigrator(
|
||||
ISqlConnectionFactory standardConnectionStandardConnectionFactory,
|
||||
ISqlConnectionFactory masterConnectionConnectionFactory,
|
||||
bool clearDatabase)
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the full migration workflow: optional drop, create-if-not-exists,
|
||||
/// then deploy DbUp scripts. Throws if the upgrade fails.
|
||||
/// </summary>
|
||||
public async Task RunAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (clearDatabase)
|
||||
{
|
||||
await DropDatabaseIfExistsAsync(cancellationToken);
|
||||
}
|
||||
|
||||
await CreateDatabaseIfNotExistsAsync(cancellationToken);
|
||||
|
||||
DatabaseUpgradeResult result = DeployMigrations();
|
||||
|
||||
if (!result.Successful)
|
||||
{
|
||||
throw result.Error;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DropDatabaseIfExistsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
string databaseName = ResolveDatabaseName();
|
||||
|
||||
await using SqlConnection connection = masterConnectionConnectionFactory.CreateConnection();
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
new CommandDefinition(
|
||||
"""
|
||||
DECLARE @statement nvarchar(max) =
|
||||
N'ALTER DATABASE ' + QUOTENAME(@databaseName) + N' SET SINGLE_USER WITH ROLLBACK IMMEDIATE;'
|
||||
+ N' DROP DATABASE ' + QUOTENAME(@databaseName) + N';';
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.databases WHERE name = @databaseName)
|
||||
EXEC sys.sp_executesql @statement;
|
||||
""",
|
||||
new { databaseName },
|
||||
cancellationToken: cancellationToken
|
||||
)
|
||||
);
|
||||
|
||||
Console.WriteLine($"Database '{databaseName}' dropped, or did not exist.");
|
||||
}
|
||||
|
||||
private async Task CreateDatabaseIfNotExistsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
string databaseName = ResolveDatabaseName();
|
||||
|
||||
await using SqlConnection connection = masterConnectionConnectionFactory.CreateConnection();
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
await connection.ExecuteAsync(
|
||||
new CommandDefinition(
|
||||
"""
|
||||
DECLARE @statement nvarchar(max);
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE name = @databaseName)
|
||||
BEGIN
|
||||
SET @statement = N'CREATE DATABASE ' + QUOTENAME(@databaseName) + N';';
|
||||
EXEC sys.sp_executesql @statement;
|
||||
END
|
||||
""",
|
||||
new { databaseName },
|
||||
cancellationToken: cancellationToken
|
||||
)
|
||||
);
|
||||
|
||||
Console.WriteLine($"Database '{databaseName}' is present.");
|
||||
}
|
||||
|
||||
private DatabaseUpgradeResult DeployMigrations()
|
||||
{
|
||||
UpgradeEngine upgrader = DeployChanges
|
||||
.To.SqlDatabase(standardConnectionStandardConnectionFactory.ConnectionString)
|
||||
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
|
||||
.WithTransactionPerScript()
|
||||
.LogToConsole()
|
||||
.Build();
|
||||
|
||||
return upgrader.PerformUpgrade();
|
||||
}
|
||||
|
||||
private string ResolveDatabaseName()
|
||||
{
|
||||
return !SqlConnectionStringHelper.TryGetDatabaseNameFromConnectionString(
|
||||
standardConnectionStandardConnectionFactory.ConnectionString,
|
||||
out var databaseName
|
||||
)
|
||||
? throw new InvalidOperationException(
|
||||
"Unable to resolve a database name from the connection string."
|
||||
)
|
||||
: databaseName;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
# Copy everything from the context (src/Core), matching Database.Seed/Dockerfile's context so
|
||||
# ProjectReference paths outside Database/ (e.g. Infrastructure.Sql) resolve.
|
||||
# ProjectReference paths resolve.
|
||||
COPY . .
|
||||
RUN dotnet restore "./Database/Database.Migrations/Database.Migrations.csproj"
|
||||
RUN dotnet build "./Database/Database.Migrations/Database.Migrations.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Database.Connection;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ISqlConnectionFactory" /> implementation that connects to the <c>master</c> database,
|
||||
/// used for create and drop operations on the target application database.
|
||||
/// </summary>
|
||||
public class MasterSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ConnectionString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (
|
||||
SqlConnectionStringHelper.TryGetMasterConnectionString(
|
||||
configuration,
|
||||
out var connectionString
|
||||
)
|
||||
)
|
||||
return connectionString;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Database connection string not configured. Set DB_SERVER, DB_USER, DB_PASSWORD env vars."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public SqlConnection CreateConnection()
|
||||
{
|
||||
return new SqlConnection(ConnectionString);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,16 @@
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using DbUp;
|
||||
using DbUp.Engine;
|
||||
using Infrastructure.Sql;
|
||||
using Database.Connection;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Entry point for the database migration runner. Reads connection details from configuration
|
||||
/// (backed by environment variables), optionally drops and recreates the target database, and
|
||||
/// applies all SQL migration scripts embedded in this assembly using DbUp.
|
||||
/// Application entry point. Responsible only for wiring up configuration
|
||||
/// and delegating the migration run to <see cref="DbMigrator"/>.
|
||||
/// </summary>
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Migration runner entry point. Drops the target database when <c>CLEAR_DATABASE</c> is
|
||||
/// set to <c>true</c>, ensures the database exists, then deploys all pending migrations.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>0</c> if migrations completed successfully; <c>1</c> if they failed or an error occurred.
|
||||
/// </returns>
|
||||
public static async Task<int> Main()
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Starting database migrations...");
|
||||
|
||||
@@ -31,150 +19,6 @@ public static class Program
|
||||
IConfiguration configuration = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
DatabaseSettings settings = DatabaseSettings.FromConfiguration(configuration);
|
||||
|
||||
if (settings.ClearDatabase)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"CLEAR_DATABASE is enabled. Dropping database '{settings.DatabaseName}'..."
|
||||
);
|
||||
await DropDatabaseIfExistsAsync(settings);
|
||||
}
|
||||
|
||||
await CreateDatabaseIfNotExistsAsync(settings);
|
||||
|
||||
return DeployMigrations(settings.ConnectionString) ? 0 : 1;
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException or SqlException)
|
||||
{
|
||||
Console.Error.WriteLine("An error occurred during database migrations:");
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the target database if it exists, first forcing it into single-user mode with
|
||||
/// rollback immediate so that any existing connections are terminated.
|
||||
/// </summary>
|
||||
/// <param name="settings">The resolved database settings.</param>
|
||||
/// <exception cref="SqlException">Thrown if the connection or the drop operation fails.</exception>
|
||||
private static async Task DropDatabaseIfExistsAsync(DatabaseSettings settings)
|
||||
{
|
||||
const string sql = """
|
||||
DECLARE @statement nvarchar(max);
|
||||
|
||||
IF EXISTS (SELECT 1 FROM sys.databases WHERE name = @databaseName)
|
||||
BEGIN
|
||||
SET @statement =
|
||||
N'ALTER DATABASE ' + QUOTENAME(@databaseName)
|
||||
+ N' SET SINGLE_USER WITH ROLLBACK IMMEDIATE;';
|
||||
EXEC sys.sp_executesql @statement;
|
||||
|
||||
SET @statement = N'DROP DATABASE ' + QUOTENAME(@databaseName) + N';';
|
||||
EXEC sys.sp_executesql @statement;
|
||||
END
|
||||
""";
|
||||
|
||||
await ExecuteNonQueryAsync(settings.MasterConnectionString, sql, settings.DatabaseName);
|
||||
Console.WriteLine($"Database '{settings.DatabaseName}' dropped, or did not exist.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the target database on the <c>master</c> connection if it does not already exist.
|
||||
/// </summary>
|
||||
/// <param name="settings">The resolved database settings.</param>
|
||||
/// <exception cref="SqlException">Thrown if the connection or the create operation fails.</exception>
|
||||
private static async Task CreateDatabaseIfNotExistsAsync(DatabaseSettings settings)
|
||||
{
|
||||
const string sql = """
|
||||
DECLARE @statement nvarchar(max);
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE name = @databaseName)
|
||||
BEGIN
|
||||
SET @statement = N'CREATE DATABASE ' + QUOTENAME(@databaseName) + N';';
|
||||
EXEC sys.sp_executesql @statement;
|
||||
END
|
||||
""";
|
||||
|
||||
await ExecuteNonQueryAsync(settings.MasterConnectionString, sql, settings.DatabaseName);
|
||||
Console.WriteLine($"Database '{settings.DatabaseName}' is present.");
|
||||
}
|
||||
|
||||
/// <param name="databaseName">The value bound to the <c>@databaseName</c> parameter.</param>
|
||||
private static async Task ExecuteNonQueryAsync(
|
||||
string connectionString,
|
||||
string sql,
|
||||
string databaseName
|
||||
)
|
||||
{
|
||||
await using SqlConnection connection = new(connectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
await using SqlCommand command = new(sql, connection);
|
||||
command.Parameters.Add("@databaseName", SqlDbType.NVarChar, 128).Value = databaseName;
|
||||
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies all pending SQL migration scripts embedded in this assembly to the target
|
||||
/// database using DbUp, logging progress to the console.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The connection string for the target application database.</param>
|
||||
/// <returns><c>true</c> if the upgrade completed successfully; otherwise <c>false</c>.</returns>
|
||||
private static bool DeployMigrations(string connectionString)
|
||||
{
|
||||
UpgradeEngine upgrader = DeployChanges
|
||||
.To.SqlDatabase(connectionString)
|
||||
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
|
||||
.WithTransactionPerScript()
|
||||
.LogToConsole()
|
||||
.Build();
|
||||
|
||||
DatabaseUpgradeResult result = upgrader.PerformUpgrade();
|
||||
|
||||
if (result.Successful)
|
||||
{
|
||||
Console.WriteLine("Database migrations completed successfully.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"Database migrations failed: {result.Error}");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connection details and runtime options resolved from environment variables.
|
||||
/// </summary>
|
||||
/// <param name="ConnectionString">The connection string for the target application database.</param>
|
||||
/// <param name="MasterConnectionString">
|
||||
/// The connection string for the <c>master</c> database, used for create and drop operations.
|
||||
/// </param>
|
||||
/// <param name="DatabaseName">The name of the target application database.</param>
|
||||
/// <param name="ClearDatabase">Whether the target database should be dropped before migrating.</param>
|
||||
private sealed record DatabaseSettings(
|
||||
string ConnectionString,
|
||||
string MasterConnectionString,
|
||||
string DatabaseName,
|
||||
bool ClearDatabase
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the settings from the <c>DB_SERVER</c>, <c>DB_NAME</c>, <c>DB_USER</c>,
|
||||
/// <c>DB_PASSWORD</c>, <c>DB_TRUST_SERVER_CERTIFICATE</c>, and <c>CLEAR_DATABASE</c>
|
||||
/// configuration values.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when a required configuration value is not set.
|
||||
/// </exception>
|
||||
public static DatabaseSettings FromConfiguration(IConfiguration configuration)
|
||||
{
|
||||
string databaseName =
|
||||
configuration["DB_NAME"]
|
||||
?? throw new InvalidOperationException(
|
||||
"The DB_NAME environment variable is not set."
|
||||
);
|
||||
|
||||
bool clearDatabase = string.Equals(
|
||||
configuration["CLEAR_DATABASE"],
|
||||
@@ -182,12 +26,17 @@ public static class Program
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
|
||||
return new DatabaseSettings(
|
||||
SqlConnectionStringHelper.BuildConnectionString(configuration, databaseName),
|
||||
SqlConnectionStringHelper.BuildMasterConnectionString(configuration),
|
||||
databaseName,
|
||||
DbMigrator migrator = new(
|
||||
new DefaultSqlConnectionFactory(configuration),
|
||||
new MasterSqlConnectionFactory(configuration),
|
||||
clearDatabase
|
||||
);
|
||||
await migrator.RunAsync();
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException or SqlException)
|
||||
{
|
||||
Console.Error.WriteLine("An error occurred during database migrations:");
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
||||
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Auth\Features.Auth.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Locations\Features.Locations.csproj" />
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
#region
|
||||
|
||||
using Infrastructure.Sql;
|
||||
using Database.Connection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Database.Seed.DatabaseHelpers;
|
||||
|
||||
public class ConnectionStrings
|
||||
public static class ConnectionStrings
|
||||
{
|
||||
public static string GetSqlServerConnectionString(IConfiguration configuration) =>
|
||||
SqlConnectionStringHelper.BuildConnectionString(configuration);
|
||||
SqlConnectionStringHelper.TryGetStandardDbConnectionString(
|
||||
configuration,
|
||||
out string connectionString
|
||||
)
|
||||
? connectionString
|
||||
: throw new InvalidOperationException(
|
||||
"Database connection string not configured. Set DB_SERVER, DB_NAME, DB_USER, DB_PASSWORD env vars."
|
||||
);
|
||||
|
||||
public static string SqliteConnectionString =>
|
||||
"Data Source=SeedData/biergarten_seed_2026-08-24T01-52-09.841693Z.sqlite";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Database.Connection.DependencyInjection;
|
||||
using Database.Seed.DatabaseHelpers;
|
||||
using Database.Seed.Geocoding;
|
||||
using Database.Seed.PipelineData;
|
||||
@@ -11,7 +12,6 @@ using Features.Locations.DependencyInjection;
|
||||
using Features.Locations.Dtos;
|
||||
using Features.Locations.Repository;
|
||||
using idunno.Password;
|
||||
using Infrastructure.Sql.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -30,7 +30,7 @@ static async Task<int> RunAsync()
|
||||
|
||||
ServiceCollection services = [];
|
||||
services.AddSingleton(configuration);
|
||||
services.AddInfrastructureSql();
|
||||
services.AddDatabaseConnection();
|
||||
services.AddFeaturesBreweries();
|
||||
services.AddFeaturesLocations();
|
||||
services.AddFeaturesAuth();
|
||||
|
||||
Reference in New Issue
Block a user