A Rask Server sample showing data persistence with EF Core + SQLite — a small product catalogue with full CRUD (list, create, edit, delete).
dotnet run --project samples/Rask.Example.EfCore
# open the printed URL at /productsThe full write-up is in docs/data-access.md. In short, it demonstrates:
IDbContextFactory<CatalogDbContext>, not a scopedDbContext. A Rask live session is long-lived over a WebSocket, so each slice opens a short-lived context per operation (await using var db = await dbContextFactory.CreateDbContextAsync(ct)).- Vertical slice architecture — code is grouped by use case under
Features/Catalog/(ListProducts,CreateProduct,EditProduct), each slice self-contained with its own form and EF Core access. No repository/service layers. App-wide shared bits live inShared/; the Catalog domain shared across slices lives inFeatures/Catalog/Shared/. - DDD tactical patterns —
Productis an aggregate root (private setters,Create/Updateonly);Money,ProductName, andStockLevelare value objects. Each value object owns its validation rule, reused verbatim by the inline form validators (Validate: Money.Validate) and enforced again on construction. - EF Core entity configuration — mapping lives in
IEntityTypeConfiguration<Product>(ApplyConfigurationsFromAssembly), with value converters for the value objects. - The SQLite decimal gotcha — SQLite has no decimal type, so
Moneyis stored as integer minor units (cents) in anINTEGERcolumn: exact, sortable, lossless. - Built-in inline validation — Rask.Core's
Validate:lambdas, no validation package.
The SQLite file (raskExampleCatalog.db, gitignored) is created and seeded on first run via
EnsureCreatedAsync(). Set RASK_DB_PATH to point at a different file (the E2E test uses this to
isolate each run). Delete the file to reset to the seed data.
tests/Rask.Example.EfCore.Tests— unit tests for the domain + EF/SQLite integration tests (round-trip + proof that price is stored asINTEGER).tests/Rask.Examples.E2E.Tests/EfCoreCrudTests.cs— a Playwright CRUD smoke test.