DbContext and the Model
Entity Framework Core's central object is the DbContext: a unit of work that opens a connection, tracks the entities you load, and translates your changes into SQL. Everything that goes wrong with EF in production traces back to misunderstanding one of three things β what an entity is, how long a context should live, or how the model got configured.
Entities and DbSets
An entity is a plain C# class mapped to a table; a DbSet<T> on your context is the
queryable entry point to it. Navigation properties (permit.Applicant,
applicant.Permits) express foreign keys as object references, which is what makes LINQ
queries read like the domain instead of like joins. Keep entities persistence-shaped: they
model the database, not the screen. Screens get view models (Module 6) β returning entities
from controllers leaks lazy-loading, serialization cycles, and over-posting vulnerabilities.
Context lifetime: one per request
The non-negotiable web rule: one DbContext per HTTP request. Shorter (one per query) and
you lose change tracking and pay connection churn; longer (shared/static/singleton) and you get
a slowly bloating tracker, stale cached entities, and β because DbContext is not
thread-safe β corrupt state under concurrent requests. The framework hands you this for free:
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(...)) in Program.cs
registers it scoped, so every controller and service in one request shares one context and
the next request gets a fresh one. Don't fight the default: no using new AppDbContext() in
controllers, and if you ever see a static AppDbContext, treat it as a production incident
waiting to happen.
Configuring the model
Two dialects say the same thing:
- Data annotations on the entity β
[Key],[Required],[MaxLength(200)]. Convenient, but couples mapping to the class and can't express everything. - Fluent API in
OnModelCreatingβ composite keys, precision, relationships, delete behavior:
modelBuilder.Entity<Permit>()
.Property(p => p.Fee).HasPrecision(18, 2);
Teams usually use annotations for simple validation-ish facts and fluent config for anything relational or subtle. Whichever you choose, be consistent β the mapping is the contract with the DBA's schema.
Migrations vs database-first reality
Migrations (dotnet ef migrations add / dotnet ef database update) version the schema
alongside the code β the clean default path, and the only schema story EF Core has. But
public-sector reality is often database-first: the schema is owned by DBAs, changed by
reviewed SQL scripts, and your job is to keep the model in sync β dotnet ef dbcontext scaffold
reverse-engineers entities and context from the live database. (Legacy note: the EDMX designer
belongs to old EF6 apps and has no EF Core equivalent.) Neither mode is wrong; what's fatal is
mixing them casually. Find out early which mode your project runs, who owns schema changes, and
how a column addition actually ships β that process knowledge matters more than any API detail.