EF Core Introduction
What EF Core is, defining entities and a DbContext, and the code-first approach.
What is Entity Framework Core?
Entity Framework Core (EF Core) is Microsoft's official object-relational mapper (ORM) for .NET — it lets you work with a database using ordinary C# classes and LINQ queries instead of writing raw SQL by hand. EF Core translates your C# code into SQL at query time, and can also generate and evolve your database schema from your C# model (the "code-first" approach covered below).
It supports every major relational database through swappable providers: SQL Server, PostgreSQL, SQLite, MySQL, and others — the same C# code mostly works unchanged if you switch providers, aside from provider-specific configuration.
Entities: plain C# classes
An entity is just a normal class that represents a database table — no base class or interface required:
public class Product
{
public int Id { get; set; } // convention: "Id" is the primary key
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int StockQuantity { get; set; }
}
EF Core uses conventions to infer most of the mapping automatically: a property named Id (or {ClassName}Id) becomes the primary key, a string becomes nvarchar, a decimal becomes a precise numeric column, and so on. Conventions can always be overridden explicitly when needed (via Fluent API or data annotations).
DbContext: the session with your database
A DbContext represents a session with the database — it tracks entities, translates LINQ into SQL, and is the object you actually query and save changes through:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlite("Data Source=shop.db");
}
}
Each DbSet<T> property corresponds to one table and is your entry point for querying and modifying rows of that entity type.
Registering DbContext with dependency injection (ASP.NET Core)
In a real application, you register DbContext with the DI container instead of hardcoding the connection string inside OnConfiguring — this is the standard pattern in ASP.NET Core apps:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
AddDbContext registers AppDbContext as a Scoped service by default — one instance per HTTP request, which matters because DbContext is not thread-safe and holds per-request change-tracking state (see the DI page in the .NET track for why Scoped is the right lifetime here).
Any class can then simply ask for it via constructor injection:
app.MapGet("/products", async (AppDbContext db) =>
{
return await db.Products.ToListAsync();
});
The code-first approach
"Code-first" means the C# entity classes are the source of truth for your schema — you write/change a Product class, then generate a migration (a versioned, reviewable script of the exact schema change) and apply it to the database. This is the opposite of "database-first," where an existing database is scaffolded into matching C# classes (also supported by EF Core, but far less common for new projects). Migrations are covered in full on the next page.
Common mistakes
- Putting real connection strings directly in source code instead of configuration (
appsettings.json+IConfiguration, or a secrets manager). - Registering
DbContextasSingleton— it isn't thread-safe, and a single shared instance across every request causes concurrency bugs and a change tracker that grows without bound. - Forgetting
awaiton EF Core's async methods (ToListAsync,SaveChangesAsync) — calling the synchronous versions works but blocks a thread pool thread unnecessarily under load.
Interview questions
Q: What is a DbContext?
The object representing a session with the database in EF Core — it exposes DbSet<T> properties for querying/modifying entities, tracks changes made to loaded entities, and translates LINQ queries into SQL when they execute.
Q: What's the difference between code-first and database-first? Code-first treats your C# entity classes as the source of truth: you model classes, then generate migrations that create/alter the database to match. Database-first goes the other direction — an existing database schema is scaffolded into matching C# entity classes. Code-first is the default, more common workflow for new projects.