Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ src/Dotnet.Samples.AspNetCore.WebApi/
├── Extensions/ — IServiceCollection extension methods (service registration)
├── Configurations/ — Options classes bound from appsettings.json
├── Middlewares/ — Custom ASP.NET Core middleware
├── Data/ — DbContext + DbInitializer (seed data)
└── Storage/ — SQLite database file (players.db)
├── Data/ — DbContext; seed data via HasData() in OnModelCreating
└── Storage/ — SQLite database file (created at runtime by MigrateAsync)

test/Dotnet.Samples.AspNetCore.WebApi.Tests/
├── Unit/ — Unit tests (controllers, services, validators)
Expand Down Expand Up @@ -206,7 +206,7 @@ This project uses Spec-Driven Development (SDD): discuss in Plan mode first, cre

**Add an endpoint**: Add DTO in `Models/` → update `PlayerMappingProfile` in `Mappings/` → add repository method(s) in `Repositories/` → add service method in `Services/` → add controller action in `Controllers/` → add/update validator rule set in `Validators/` → add tests in `test/.../Unit/` → run pre-commit checks.

**Modify schema**: Update `Player` entity → update DTOs → update AutoMapper profile → reset `Storage/players.db` → update tests → run `dotnet test`.
**Modify schema**: Update `Player` entity → update DTOs → update AutoMapper profile → update `HasData()` seed data in `OnModelCreating` if needed → run `dotnet ef migrations add <Name>` → update tests → run `dotnet test`.

## Architecture Decision Records (ADRs)

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ logs/
bin/
obj/
TestResults/
storage/*.db
.claude/settings.local.json
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc

### Changed

- Replace pre-seeded `storage/players-sqlite3.db` binary blob with EF Core `MigrateAsync()` at startup: schema and seed data are now applied automatically before the first request is served; `STORAGE_PATH` env var controls the database file path (Docker volume path in production, `AppContext.BaseDirectory/storage/` locally); the committed database file, `Dockerfile` db copy step, and `scripts/run-migrations-and-copy-database.sh` have been removed (#459)
- Recreate EF Core migrations using `HasData()` in `OnModelCreating`: three self-contained migrations (`InitialCreate` DDL, `SeedStarting11` DML, `SeedSubstitutes` DML) generated by EF Core with literal `InsertData` values — no migration calls application methods; `NormalizePlayerDataset` patch migration eliminated by folding corrections into seed data from the start (#459)
- Replace `DatabaseFakes.CreateTable()` (placeholder schema) and `DatabaseFakes.Seed()` (manual insert bypassing migrations) with `DatabaseFakes.MigrateAsync()`, which applies the full EF Core migration chain on in-memory SQLite (#459)
- Switch runtime base image from `mcr.microsoft.com/dotnet/aspnet:10.0` (Debian)
to `mcr.microsoft.com/dotnet/aspnet:10.0-alpine` (before: 113.4 MB →
after: 73.9 MB compressed; measured via `docker manifest inspect` and
Expand Down
5 changes: 0 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ WORKDIR /src
COPY src/Dotnet.Samples.AspNetCore.WebApi/*.csproj ./Dotnet.Samples.AspNetCore.WebApi/
RUN dotnet restore ./Dotnet.Samples.AspNetCore.WebApi

# Copy source code and pre-seeded SQLite database
COPY src/Dotnet.Samples.AspNetCore.WebApi/ ./Dotnet.Samples.AspNetCore.WebApi/

WORKDIR /src/Dotnet.Samples.AspNetCore.WebApi
Expand Down Expand Up @@ -53,10 +52,6 @@ COPY --chmod=444 README.md ./
# Copy entrypoint and healthcheck scripts
COPY --chmod=555 scripts/entrypoint.sh ./entrypoint.sh
COPY --chmod=555 scripts/healthcheck.sh ./healthcheck.sh
# The 'hold' is our storage compartment within the image. Here, we copy a
# pre-seeded SQLite database file, which Compose will mount as a persistent
# 'storage' volume when the container starts up.
COPY --from=builder /src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db ./hold/players-sqlite3.db

# Add non-root user and make volume mount point writable
RUN addgroup -S aspnetcore && \
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Before you begin, ensure you have the following installed:

- .NET 10 SDK (LTS) or higher
- Docker Desktop (optional, for containerized deployment)
- dotnet-ef CLI tool (for database migrations)
- dotnet-ef CLI tool (optional, for creating new migrations)

```bash
dotnet tool install --global dotnet-ef
Expand Down Expand Up @@ -206,7 +206,7 @@ docker compose build
docker compose up
```

> 💡 On first run, the container copies a pre-seeded SQLite database into a persistent volume. On subsequent runs, that volume is reused and the data is preserved.
> 💡 On first run, the app applies EF Core migrations and seeds the database automatically into a persistent volume. On subsequent runs, that volume is reused and the data is preserved.

### Stop the application

Expand All @@ -216,7 +216,7 @@ docker compose down

### Reset the database

To remove the volume and reinitialize the database from the built-in seed file:
To remove the volume and let the app re-create and re-seed the database on next startup:

```bash
docker compose down -v
Expand Down Expand Up @@ -306,8 +306,7 @@ dotnet test --results-directory "coverage" --collect:"XPlat Code Coverage" --set
| `dotnet test --collect:"XPlat Code Coverage"` | Run tests with coverage report |
| `dotnet csharpier .` | Format source code |
| `dotnet ef migrations add <Name>` | Create a new migration |
| `dotnet ef database update` | Apply migrations |
| `./scripts/run-migrations-and-copy-database.sh` | Regenerate database with seed data |
| `dotnet ef database update` | Apply migrations manually |
| `docker compose build` | Build Docker image |
| `docker compose up` | Start Docker container |
| `docker compose down` | Stop Docker container |
Expand Down
4 changes: 2 additions & 2 deletions adr/0003-use-sqlite-for-data-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ The cross-language comparison set (Go/Gin, Java/Spring Boot, Python/FastAPI, Rus

## Decision

We will use SQLite as the database engine, accessed through Entity Framework Core. The database file is stored at `storage/players-sqlite3.db` and is pre-seeded with sample data. Docker deployments mount the file into a named volume so data survives container restarts.
We will use SQLite as the database engine, accessed through Entity Framework Core. The database file is created at `storage/players-sqlite3.db` at runtime: EF Core applies pending migrations (schema + seed data via `HasData()`) automatically at startup via `MigrateAsync()` before the first request is served. Docker deployments mount the file into a named volume so data survives container restarts.

## Consequences

### Positive
- Zero-config: no server process, no connection string credentials, no Docker service dependency for local development.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- The database file can be committed to the repository as seed data, making onboarding instant.
- EF Core abstracts the SQL dialect, so migrating to another database requires changing only the provider registration.
- `MigrateAsync()` at startup ensures the schema is always up to date, making onboarding instant without committing binary database files.

### Negative
- SQLite does not support concurrent writes, making it unsuitable for multi-instance deployments or high-throughput scenarios.
Expand Down
16 changes: 4 additions & 12 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,15 @@ log() {
return 0
}

IMAGE_STORAGE_PATH="/app/hold/players-sqlite3.db"
VOLUME_STORAGE_PATH="/storage/players-sqlite3.db"

log "✔ Starting container..."

VOLUME_STORAGE_PATH="${STORAGE_PATH:-/storage/players-sqlite3.db}"

if [ ! -f "$VOLUME_STORAGE_PATH" ]; then
log "⚠️ No existing database file found in volume."
if [ -f "$IMAGE_STORAGE_PATH" ]; then
log "🔄 Copying database file to writable volume..."
cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH"
log "✔ Database initialized at $VOLUME_STORAGE_PATH"
else
log "⚠️ Database file missing at $IMAGE_STORAGE_PATH"
exit 1
fi
log "🗄️ EF Core migrations will initialize the database on first start."
else
log "✔ Existing database file found. Skipping seed copy."
log "✔ Existing database file found at $VOLUME_STORAGE_PATH."
fi

log "✔ Ready!"
Expand Down
66 changes: 0 additions & 66 deletions scripts/run-migrations-and-copy-database.sh

This file was deleted.

3 changes: 3 additions & 0 deletions src/Dotnet.Samples.AspNetCore.WebApi/Data/PlayerDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dotnet.Samples.AspNetCore.WebApi.Models;
using Dotnet.Samples.AspNetCore.WebApi.Utilities;
using Microsoft.EntityFrameworkCore;

namespace Dotnet.Samples.AspNetCore.WebApi.Data;
Expand Down Expand Up @@ -33,6 +34,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
entity.HasKey(player => player.Id);
entity.Property(player => player.Id).ValueGeneratedOnAdd();
entity.HasIndex(player => player.SquadNumber).IsUnique();
entity.HasData(PlayerData.MakeStarting11WithId());
entity.HasData(PlayerData.GetSubstitutesWithId());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
</ItemGroup>

<ItemGroup>
<Content Include="storage/players-sqlite3.db">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
</ItemGroup>

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ IWebHostEnvironment environment
{
services.AddDbContextPool<PlayerDbContext>(options =>
{
var dataSource = Path.Combine(
AppContext.BaseDirectory,
"storage",
"players-sqlite3.db"
);
var dataSource =
Environment.GetEnvironmentVariable("STORAGE_PATH")
?? Path.Combine(AppContext.BaseDirectory, "storage", "players-sqlite3.db");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

var storageDir = Path.GetDirectoryName(dataSource);
if (!string.IsNullOrWhiteSpace(storageDir))
{
Directory.CreateDirectory(storageDir);
}
options.UseSqlite($"Data Source={dataSource}");

if (environment.IsDevelopment())
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading