Add database seeding to dev env

This commit is contained in:
Aaron Po
2026-02-07 18:32:50 -05:00
parent 393e57af7f
commit 17bf29700a
8 changed files with 237 additions and 67 deletions

View File

@@ -35,15 +35,36 @@ services:
DOTNET_RUNNING_IN_CONTAINER: "true"
DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
MASTER_DB_CONNECTION_STRING: "${MASTER_DB_CONNECTION_STRING}"
CLEAR_DATABASE: "true"
restart: "no"
networks:
- devnet
database.seed:
image: database.seed
container_name: dev-env-database-seed
depends_on:
database.migrations:
condition: service_completed_successfully
build:
context: ./src/Core
dockerfile: Database/Database.Seed/Dockerfile
args:
BUILD_CONFIGURATION: Release
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
restart: "no"
networks:
- devnet
api.core:
image: api.core
container_name: dev-env-api-core
depends_on:
sqlserver:
condition: service_healthy
database.seed:
condition: service_completed_successfully
build:
context: ./src/Core
dockerfile: API/API.Core/Dockerfile

94
docker-compose.test.yaml Normal file
View File

@@ -0,0 +1,94 @@
services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
container_name: test-env-sqlserver
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "${SA_PASSWORD}"
MSSQL_PID: "Express"
DOTNET_RUNNING_IN_CONTAINER: "true"
volumes:
- sqlserverdata-test:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${SA_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
networks:
- testnet
database.migrations:
image: database.migrations
container_name: test-env-database-migrations
depends_on:
sqlserver:
condition: service_healthy
build:
context: ./src/Core/Database
dockerfile: Database.Migrations/Dockerfile
args:
BUILD_CONFIGURATION: Release
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
MASTER_DB_CONNECTION_STRING: "${TEST_MASTER_DB_CONNECTION_STRING}"
CLEAR_DATABASE: "true"
restart: "no"
networks:
- testnet
database.seed:
image: database.seed
container_name: test-env-database-seed
depends_on:
database.migrations:
condition: service_completed_successfully
build:
context: ./src/Core
dockerfile: Database/Database.Seed/Dockerfile
args:
BUILD_CONFIGURATION: Release
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
restart: "no"
networks:
- testnet
api.specs:
image: api.specs
container_name: test-env-api-specs
depends_on:
sqlserver:
condition: service_healthy
database.migrations:
condition: service_completed_successfully
database.seed:
condition: service_completed_successfully
build:
context: ./src/Core
dockerfile: API/API.Specs/Dockerfile
args:
BUILD_CONFIGURATION: Release
environment:
ASPNETCORE_ENVIRONMENT: "Test"
DOTNET_RUNNING_IN_CONTAINER: "true"
API_BASE_URL: "http://api.core:8080"
DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
volumes:
- ./test-results:/app/test-results
restart: "no"
networks:
- testnet
volumes:
sqlserverdata-test:
driver: local
networks:
testnet:
driver: bridge

View File

@@ -0,0 +1,24 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"]
COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"]
COPY ["Repository/Repository.Core/Repository.Core.csproj", "Repository/Repository.Core/"]
COPY ["Service/Service.Core/Service.Core.csproj", "Service/Service.Core/"]
RUN dotnet restore "API/API.Specs/API.Specs.csproj"
COPY . .
WORKDIR "/src/API/API.Specs"
RUN dotnet build "./API.Specs.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./API.Specs.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS final
WORKDIR /src
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copy the entire source tree for testing
COPY . .
RUN mkdir -p /app/test-results
WORKDIR /src/API/API.Specs
ENTRYPOINT ["dotnet", "test", "API.Specs.csproj", "-c", "Release", "--logger", "trx;LogFileName=/app/test-results/test-results.trx"]

View File

@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DataLayer</RootNamespace>
<RootNamespace>Database.Migrations</RootNamespace>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

View File

@@ -3,7 +3,7 @@ using System.Reflection;
using DbUp;
using Microsoft.Data.SqlClient;
namespace DataLayer;
namespace Database.Migrations;
public static class Program
{
@@ -22,6 +22,57 @@ public static class Program
return result.Successful;
}
private static bool ClearDatabase()
{
var myConn = new SqlConnection(masterConnectionString);
try
{
myConn.Open();
// First, set the database to single user mode to close all connections
var setModeCommand = new SqlCommand(
"IF EXISTS (SELECT 1 FROM sys.databases WHERE name = 'Biergarten') " +
"ALTER DATABASE [Biergarten] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;",
myConn);
try
{
setModeCommand.ExecuteNonQuery();
Console.WriteLine("Database set to single user mode.");
}
catch (System.Exception ex)
{
Console.WriteLine($"Warning: Could not set single user mode: {ex.Message}");
}
// Then drop the database
var dropCommand = new SqlCommand("DROP DATABASE IF EXISTS [Biergarten];", myConn);
try
{
dropCommand.ExecuteNonQuery();
Console.WriteLine("Database cleared successfully.");
}
catch (System.Exception ex)
{
Console.WriteLine($"Error dropping database: {ex}");
return false;
}
}
catch (System.Exception ex)
{
Console.WriteLine($"Error clearing database: {ex}");
return false;
}
finally
{
if (myConn.State == ConnectionState.Open)
{
myConn.Close();
}
}
return true;
}
private static bool CreateDatabaseIfNotExists()
{
var myConn = new SqlConnection(masterConnectionString);
@@ -58,6 +109,13 @@ public static class Program
try
{
var clearDatabase = Environment.GetEnvironmentVariable("CLEAR_DATABASE");
if (clearDatabase == "true")
{
Console.WriteLine("CLEAR_DATABASE is enabled. Clearing existing database...");
ClearDatabase();
}
CreateDatabaseIfNotExists();
var success = DeployMigrations();

View File

@@ -4,7 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DBSeed</RootNamespace>
<RootNamespace>Database.Seed</RootNamespace>
</PropertyGroup>
<ItemGroup>
@@ -18,7 +18,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Database.Migrations\Database.Migrations.csproj" />
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
# Copy everything from the context (src/Core)
COPY . .
RUN dotnet restore "./Database/Database.Seed/Database.Seed.csproj"
RUN dotnet build "./Database/Database.Seed/Database.Seed.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Database/Database.Seed/Database.Seed.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/runtime:10.0 AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Database.Seed.dll"]

View File

@@ -5,57 +5,14 @@ using System.Reflection;
try
{
var connectionString = Environment.GetEnvironmentVariable(
"DB_CONNECTION_STRING"
);
if (string.IsNullOrWhiteSpace(connectionString))
throw new InvalidOperationException(
"Environment variable DB_CONNECTION_STRING is not set or is empty."
);
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
// drop and recreate the database
var useMaster = connection.CreateCommand();
useMaster.CommandText = "USE master;";
await useMaster.ExecuteNonQueryAsync();
var dbName = "Biergarten";
var dropDb = connection.CreateCommand();
dropDb.CommandText = $@"
IF DB_ID(N'{dbName}') IS NOT NULL
BEGIN
ALTER DATABASE [{dbName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [{dbName}];
END";
await dropDb.ExecuteNonQueryAsync();
var createDb = connection.CreateCommand();
createDb.CommandText = $@"CREATE DATABASE [{dbName}];";
await createDb.ExecuteNonQueryAsync();
await connection.CloseAsync();
await connection.OpenAsync();
var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
Console.WriteLine("Connected to database.");
Console.WriteLine("Starting seeding...");
Console.WriteLine("Starting migrations...");
// Run Database.Core migrations (embedded resources) via DbUp
var migrationAssembly = Assembly.Load("Database.Core");
var upgrader = DeployChanges
.To.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(migrationAssembly)
.LogToConsole()
.Build();
var upgradeResult = upgrader.PerformUpgrade();
if (!upgradeResult.Successful)
throw upgradeResult.Error;
Console.WriteLine("Migrations completed.");
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
ISeeder[] seeders =
[
@@ -71,7 +28,8 @@ try
}
Console.WriteLine("Seed completed successfully.");
await connection.CloseAsync();
}
return 0;
}
catch (Exception ex)