Skip to content

Commit 68d3f0d

Browse files
nanotaboadaclaude
andcommitted
feat(data): replace pre-seeded db with EF Core migrations and HasData() (#459)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 66eca79 commit 68d3f0d

29 files changed

Lines changed: 1652 additions & 658 deletions

.github/copilot-instructions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ src/Dotnet.Samples.AspNetCore.WebApi/
3434
├── Extensions/ — IServiceCollection extension methods (service registration)
3535
├── Configurations/ — Options classes bound from appsettings.json
3636
├── Middlewares/ — Custom ASP.NET Core middleware
37-
├── Data/ — DbContext + DbInitializer (seed data)
38-
└── Storage/ — SQLite database file (players.db)
37+
├── Data/ — DbContext; seed data via HasData() in OnModelCreating
38+
└── Storage/ — SQLite database file (created at runtime by MigrateAsync)
3939
4040
test/Dotnet.Samples.AspNetCore.WebApi.Tests/
4141
├── Unit/ — Unit tests (controllers, services, validators)
@@ -206,7 +206,7 @@ This project uses Spec-Driven Development (SDD): discuss in Plan mode first, cre
206206

207207
**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.
208208

209-
**Modify schema**: Update `Player` entity → update DTOs → update AutoMapper profile → reset `Storage/players.db` → update tests → run `dotnet test`.
209+
**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`.
210210

211211
## Architecture Decision Records (ADRs)
212212

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ logs/
22
bin/
33
obj/
44
TestResults/
5+
storage/*.db
56
.claude/settings.local.json

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ This project uses famous football stadiums (A-Z) that hosted FIFA World Cup matc
5454

5555
### Changed
5656

57+
- 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)
58+
- 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)
59+
- 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)
5760
- Switch runtime base image from `mcr.microsoft.com/dotnet/aspnet:10.0` (Debian)
5861
to `mcr.microsoft.com/dotnet/aspnet:10.0-alpine` (before: 113.4 MB →
5962
after: 73.9 MB compressed; measured via `docker manifest inspect` and

Dockerfile

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ WORKDIR /src
1010
COPY src/Dotnet.Samples.AspNetCore.WebApi/*.csproj ./Dotnet.Samples.AspNetCore.WebApi/
1111
RUN dotnet restore ./Dotnet.Samples.AspNetCore.WebApi
1212

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

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

6156
# Add non-root user and make volume mount point writable
6257
RUN addgroup -S aspnetcore && \

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ Before you begin, ensure you have the following installed:
161161

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

166166
```bash
167167
dotnet tool install --global dotnet-ef
@@ -206,7 +206,7 @@ docker compose build
206206
docker compose up
207207
```
208208

209-
> 💡 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.
209+
> 💡 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.
210210
211211
### Stop the application
212212

@@ -216,7 +216,7 @@ docker compose down
216216

217217
### Reset the database
218218

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

221221
```bash
222222
docker compose down -v
@@ -306,8 +306,7 @@ dotnet test --results-directory "coverage" --collect:"XPlat Code Coverage" --set
306306
| `dotnet test --collect:"XPlat Code Coverage"` | Run tests with coverage report |
307307
| `dotnet csharpier .` | Format source code |
308308
| `dotnet ef migrations add <Name>` | Create a new migration |
309-
| `dotnet ef database update` | Apply migrations |
310-
| `./scripts/run-migrations-and-copy-database.sh` | Regenerate database with seed data |
309+
| `dotnet ef database update` | Apply migrations manually |
311310
| `docker compose build` | Build Docker image |
312311
| `docker compose up` | Start Docker container |
313312
| `docker compose down` | Stop Docker container |

adr/0003-use-sqlite-for-data-storage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ The cross-language comparison set (Go/Gin, Java/Spring Boot, Python/FastAPI, Rus
1414

1515
## Decision
1616

17-
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.
17+
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.
1818

1919
## Consequences
2020

2121
### Positive
2222
- Zero-config: no server process, no connection string credentials, no Docker service dependency for local development.
23-
- The database file can be committed to the repository as seed data, making onboarding instant.
2423
- EF Core abstracts the SQL dialect, so migrating to another database requires changing only the provider registration.
24+
- `MigrateAsync()` at startup ensures the schema is always up to date, making onboarding instant without committing binary database files.
2525

2626
### Negative
2727
- SQLite does not support concurrent writes, making it unsuitable for multi-instance deployments or high-throughput scenarios.

scripts/entrypoint.sh

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,15 @@ log() {
88
return 0
99
}
1010

11-
IMAGE_STORAGE_PATH="/app/hold/players-sqlite3.db"
12-
VOLUME_STORAGE_PATH="/storage/players-sqlite3.db"
13-
1411
log "✔ Starting container..."
1512

13+
VOLUME_STORAGE_PATH="${STORAGE_PATH:-/storage/players-sqlite3.db}"
14+
1615
if [ ! -f "$VOLUME_STORAGE_PATH" ]; then
1716
log "⚠️ No existing database file found in volume."
18-
if [ -f "$IMAGE_STORAGE_PATH" ]; then
19-
log "🔄 Copying database file to writable volume..."
20-
cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH"
21-
log "✔ Database initialized at $VOLUME_STORAGE_PATH"
22-
else
23-
log "⚠️ Database file missing at $IMAGE_STORAGE_PATH"
24-
exit 1
25-
fi
17+
log "🗄️ EF Core migrations will initialize the database on first start."
2618
else
27-
log "✔ Existing database file found. Skipping seed copy."
19+
log "✔ Existing database file found at $VOLUME_STORAGE_PATH."
2820
fi
2921

3022
log "✔ Ready!"

scripts/run-migrations-and-copy-database.sh

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/Dotnet.Samples.AspNetCore.WebApi/Data/PlayerDbContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Dotnet.Samples.AspNetCore.WebApi.Models;
2+
using Dotnet.Samples.AspNetCore.WebApi.Utilities;
23
using Microsoft.EntityFrameworkCore;
34

45
namespace Dotnet.Samples.AspNetCore.WebApi.Data;
@@ -33,6 +34,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
3334
entity.HasKey(player => player.Id);
3435
entity.Property(player => player.Id).ValueGeneratedOnAdd();
3536
entity.HasIndex(player => player.SquadNumber).IsUnique();
37+
entity.HasData(PlayerData.MakeStarting11WithId());
38+
entity.HasData(PlayerData.GetSubstitutesWithId());
3639
});
3740
}
3841
}

src/Dotnet.Samples.AspNetCore.WebApi/Dotnet.Samples.AspNetCore.WebApi.csproj

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@
2525
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
2626
</ItemGroup>
2727

28-
<ItemGroup>
29-
<Content Include="storage/players-sqlite3.db">
30-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
31-
<PackageCopyToOutput>true</PackageCopyToOutput>
32-
</Content>
33-
</ItemGroup>
34-
3528
<PropertyGroup>
3629
<GenerateDocumentationFile>true</GenerateDocumentationFile>
3730
<NoWarn>$(NoWarn);1591</NoWarn>

0 commit comments

Comments
 (0)