Real Data Architecture: EF Core, Transactions, Repositories & Commands
The EnterpriseOps Command Center's Approvals screen, in front of a real Entity Framework Core
database. The fake WorkOrderService of the earlier modules is gone: Create, Update, Approve, Search
and Audit are EF Core operations behind a service boundary, each with an explicit transaction boundary
and every database failure mapped to a result code, a user message and an audit row.
The screen is the approve panel next to the work queue: pick a work order, type a comment,
click Approve. The handler builds an ApproveWorkOrderCommand, hands it to the service and shows the
CommandResult; the service authorizes, creates a short-lived DbContext, opens a transaction, loads by
tenant + id, checks the version, validates the transition, persists, audits and commits.
EF Core. Microsoft.EntityFrameworkCore.Sqlite 10.0.12, over an in-memory SQLite database
(DataSource=:memory:) created fresh per session: no file, no server, no network.
Run it
cd "Module 4/EnterpriseOps"
dotnet run -f net10.0 --urls http://localhost:5204
Then open http://localhost:5204. Every browser session gets its own database and its own
SessionContext — nothing is shared and nothing is static.
Signed in as ana.ops (Manager), tenant fabrikam, with 60 seeded work orders across three
tenants. Work order WO-2002 — Repair loading dock pump is selected on load: OnHold, v8, exactly
as it.
What to click
| Control | Path | What you should see |
|---|---|---|
Approve with WO-2002 selected | failure (the first click) | Red banner "This work order is on hold — resolve the hold before approving.", detail WO_STATE_INVALID · transaction rolled back · nothing persisted, status bar CommandResult.Fail — WO_STATE_INVALID · rolled back · audited |
Approve with an InProgress row selected | success (the second click) | Green banner "Work order approved.", detail committed · audit written · correlation …, status bar CommandResult.Ok — committed · v3 → v4 · audited; the grid refreshes and the row is Completed with a new version |
| Approve with the comment box emptied | validation | VALIDATION_FAILED — "An approval comment is required." Validation happens in the service, not in the screen |
| Cancel | UI only | Clears the comment and the banner; nothing is sent to the service |
| txtSearch + Search | read side | The grid reloads with WorkQueueRow projections (one short-lived DbContext, AsNoTracking) |
| Audit log | dialog | await dialog.ShowDialogAsync() over AuditLogRow projections: every committed and every rejected command with its code, user and correlation id. The query service checks the ReadAudit permission before running anything |
The services write each step (Security:, Data:, Service:, Audit:) to the server log through
Services/ActivityTrace.cs → System.Diagnostics.Trace (the debugger's Output window).
Where things live
EnterpriseOps/
Program.cs Application.MainPage = new UI.ApprovalsPage();
UI/ ApprovalsPage (work queue · approve panel · status bar), AuditLogDialog
Domain/ WorkOrder, AuditEntry, Tenant, WorkOrderStatus, Priority, WorkOrderTransitions
Services/ IWorkOrderCommandService, IWorkOrderQueryService, SessionContext,
IActivityTrace + ActivityTrace (server log) + NullTrace (tests),
Commands/ (Create/Update/ApproveWorkOrderCommand, CommandContext, CommandResult),
Queries/ (WorkQueueQuery, WorkQueueRow, PagedResult<T>, AuditLogRow, AuditQueryResult)
Data/ EnterpriseOpsDbContext, SessionDatabase, SeedData, IWorkOrderRepository, WorkOrderRepository,
WorkOrderCommandService, WorkOrderQueryService, ErrorMap, DataArchitecturePatterns (the file)
Security/ Permissions (roles, operations, server-side rules)
docs/ the five deliverables + the boundary SVG
Production readiness
- Would not change. The boundary, the command and result classes, the transaction shape, the error codes and messages, the per-operation context lifetime, the audit-after-rollback rule.
- Provider. Swap
UseSqlite(connection)forUseSqlServer(connectionString)inSessionDatabase, drop the session-long connection (a real server has a pool), and swap the provider checks inErrorMap.Map(SQL Server: 2601/2627 unique, 547 foreign key, −2 timeout). The result codes stay. - Concurrency token.
int Versionbecomesrowversion(byte[]), as theApproveWorkOrderCommandsketches. - Schema.
EnsureCreated()+SeedDatabecomes EF Core migrations applied by the pipeline. - Concurrency control.
SessionDatabase.Gateexists because one in-memory SQLite connection is shared by a session; with a pooled connection it can go. - Retries. A real provider needs a transient-fault policy (EF Core's execution strategy) around the transaction.
- Diagnostics. The
ActivityTracelines become structured log events keyed by the correlation id (Module 11).