Entity Framework Core
Entity Framework Core (EF Core) is the object-relational mapper for .NET. You describe your data as C# classes and a DbContext, write queries in LINQ, and EF Core translates them into SQL for SQL Server, PostgreSQL, SQLite, MySQL, and other databases. It tracks changes to loaded entities and saves them in a single unit of work, and migrations evolve the database schema alongside the code.
EF Core removes a great deal of data-access boilerplate, but it is not a way to avoid understanding SQL. Most production problems with EF Core — slow pages, timeouts, unexpected memory use — come from queries that translate into something other than what the developer imagined. Knowing how translation, tracking, and loading work keeps it fast.
TL;DR
- A
DbContextexposesDbSet<T>collections and maps classes to tables, configured by conventions and the fluent API. - LINQ queries are translated to SQL; only
ToListAsync,FirstAsync, and similar calls execute them. - Change tracking records modifications;
SaveChangesAsyncwrites them in one transaction. - Use
AsNoTrackingand projections (Selectinto DTOs) for read-only queries. - Use
ExecuteUpdateAsync/ExecuteDeleteAsyncfor bulk changes without loading entities. - Manage schema with migrations, apply them deliberately in deployment, and handle optimistic concurrency with concurrency tokens.
Quick Example
A context, an entity, and efficient queries:
Registration in ASP.NET Core, with context pooling:
Core Concepts
Modeling
EF Core builds a model from conventions (an Id property is the key; a navigation plus foreign key forms a relationship), data annotations, and the fluent API in OnModelCreating. The fluent API is the most expressive: keys, indexes, precision, relationships and delete behavior, table splitting, owned and complex types for value objects, value converters for enums or strongly typed IDs, and JSON columns. Global query filters apply conditions automatically — useful for soft deletes and multi-tenancy, as long as everyone knows they exist.
Query Translation
A LINQ query is an expression tree until it is enumerated. EF Core translates it into SQL; methods it cannot translate cause an exception rather than silent client-side evaluation (except in the final projection). Inspect the SQL with ToQueryString(), logging (LogTo), or a profiler. FromSql and SqlQuery run raw SQL when LINQ is not the right tool — with interpolated values automatically turned into parameters.
Change Tracking
Entities loaded by a tracking query are snapshotted. When SaveChangesAsync runs, EF Core detects changes and issues inserts, updates, and deletes in a transaction, in dependency order. Tracking costs memory and CPU, so read-only paths should use AsNoTracking() or projections. A DbContext is a short-lived unit of work: one per request, never shared across threads.
Loading Related Data
Concurrency
Configure a concurrency token — rowversion on SQL Server, xmin on PostgreSQL, or a version column. Updates include the original value in their WHERE clause; if another writer changed the row, EF Core throws DbUpdateConcurrencyException, and the application decides whether to retry, merge, or report a conflict.
Migrations
Review generated migrations like any code: they can drop columns or rebuild tables. In production, apply migrations as an explicit deployment step — an idempotent SQL script or a migration bundle — rather than calling Database.Migrate() from every application instance at startup. Use expand-and-contract changes so old and new application versions work during rollout. See Database Migrations.
Providers
Providers differ in translation support and types; test with the provider you deploy on.
Performance Habits
- Project to DTOs instead of loading full entity graphs.
- Paginate with keyset (seek) pagination for large tables rather than large
Skipvalues. - Index the columns your queries filter and sort on; EF Core migrations can create them.
- Batch related work in one
SaveChangesAsync; avoid calling it inside loops. - Use compiled queries for very hot paths, and DbContext pooling for high request rates.
- Watch logs for query counts per request to catch N+1 patterns early.
Best Practices
Keep the DbContext Short-Lived
Create one per request or unit of work through dependency injection. Long-lived contexts accumulate tracked entities and stale data.
Read the SQL
Log generated SQL in development and review it for hot queries. If the SQL surprises you, rewrite the LINQ or use raw SQL.
Test Against a Real Database
The in-memory provider does not behave like a relational database (no transactions, constraints, or SQL translation). Use SQLite carefully, or better, the real engine in containers. See Object-Relational Mapping.
Make Migrations a Reviewed Deployment Step
Generate scripts in CI, review them, and apply them once per deployment with appropriate permissions.
Common Mistakes
N+1 Queries in Loops
Calling ToList Too Early
Migrating at Startup From Every Instance
FAQ
What is Entity Framework Core?
The object-relational mapper for .NET. It maps C# classes to database tables, translates LINQ queries to SQL, tracks changes to loaded objects, and manages schema changes with migrations.
Is EF Core slow?
Not inherently. Most performance problems come from N+1 queries, loading too much data, unnecessary tracking, or untranslatable queries. With projections, no-tracking reads, proper indexes, and bulk operations, EF Core performs well for most workloads.
Should I use EF Core or Dapper?
EF Core offers change tracking, migrations, and LINQ; Dapper is a thin, very fast mapper over hand-written SQL. Many teams use EF Core for most work and Dapper or raw SQL for specific performance-critical queries.
How do I handle concurrency conflicts?
Configure a concurrency token such as a row version. EF Core then throws DbUpdateConcurrencyException when a row changed since it was read, and your code decides whether to retry, merge, or report the conflict.
Should I use the in-memory provider for tests?
Generally not for data-access tests, because it does not behave like a relational database. Use the real database engine in a container, or SQLite with awareness of its differences.
Related Topics
- C# — The language EF Core is used from
- Object-Relational Mapping — ORM concepts and trade-offs
- Database Migrations — Evolving schemas safely
- SQL Server — The most common EF Core database
- PostgreSQL — Supported through Npgsql
- Database Transactions — Units of work and isolation