Skip to main content

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

ControlPathWhat you should see
Approve with WO-2002 selectedfailure (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 selectedsuccess (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 emptiedvalidationVALIDATION_FAILED — "An approval comment is required." Validation happens in the service, not in the screen
CancelUI onlyClears the comment and the banner; nothing is sent to the service
txtSearch + Searchread sideThe grid reloads with WorkQueueRow projections (one short-lived DbContext, AsNoTracking)
Audit logdialogawait 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.csSystem.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) for UseSqlServer(connectionString) in SessionDatabase, drop the session-long connection (a real server has a pool), and swap the provider checks in ErrorMap.Map (SQL Server: 2601/2627 unique, 547 foreign key, −2 timeout). The result codes stay.
  • Concurrency token. int Version becomes rowversion (byte[]), as the ApproveWorkOrderCommand sketches.
  • Schema. EnsureCreated() + SeedData becomes EF Core migrations applied by the pipeline.
  • Concurrency control. SessionDatabase.Gate exists 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 ActivityTrace lines become structured log events keyed by the correlation id (Module 11).