100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Entity Framework Core Integration

How to wire Entity Framework Core into an ASP.NET Core application, from DbContext registration to migrations and query patterns.

Data & ServicesIntermediate10 min readJul 10, 2026
Analogies

Registering DbContext in ASP.NET Core

Entity Framework Core (EF Core) is the object-relational mapper that lets ASP.NET Core applications talk to a relational database using C# classes instead of raw SQL. Integration starts with defining a class that derives from DbContext, exposing DbSet<T> properties for each entity, and registering it with the dependency injection container via AddDbContext in Program.cs. AddDbContext wires up the context with a scoped lifetime by default, which matches the per-HTTP-request unit-of-work pattern EF Core relies on.

🏏

Cricket analogy: Registering DbContext is like a franchise registering its squad list with the league before the season starts — until that registration happens, no match (HTTP request) can use the players (entities) at all.

Connection strings and provider selection happen inside the AddDbContext lambda, typically calling options.UseSqlServer(connectionString) or options.UseNpgsql(connectionString) for PostgreSQL. The connection string itself should live in appsettings.json or, in production, in a secret store like Azure Key Vault, and be pulled in through IConfiguration rather than hardcoded. Because DbContext is registered as scoped, EF Core guarantees that all repositories and services resolved within a single HTTP request share the exact same context instance and therefore the same change-tracker and identity map.

🏏

Cricket analogy: Choosing a provider is like a team choosing which pitch conditions to prepare for — a turning pitch (SQL Server) versus a green seamer (PostgreSQL) — the strategy underneath adapts but the scorecard format stays the same.

Migrations and Schema Evolution

Migrations are EF Core's mechanism for evolving the database schema alongside your C# model changes in a version-controlled, repeatable way. Running dotnet ef migrations add <Name> inspects the current model snapshot, diffs it against the previous one, and generates a migration class with Up and Down methods describing the schema delta. Applying dotnet ef database update executes pending migrations in order against the target database, and because migrations are just C# files checked into source control, they travel with the codebase through code review, CI, and deployment pipelines.

🏏

Cricket analogy: A migration is like a substitution made mid-innings that gets recorded in the official scorebook — the Up method is the change coming in, the Down method is the record of who it replaced, so the match history stays auditable.

csharp
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("Default"),
        sql => sql.MigrationsAssembly("MyApp.Infrastructure")));

// AppDbContext.cs
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Course> Courses => Set<Course>();
    public DbSet<Enrollment> Enrollments => Set<Enrollment>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Enrollment>()
            .HasIndex(e => new { e.StudentId, e.CourseId })
            .IsUnique();
    }
}

// CLI
// dotnet ef migrations add InitialCreate -o Data/Migrations
// dotnet ef database update

EF Core's change tracker only detects modifications on entities it is currently tracking. Calls like AsNoTracking() on read-only queries skip that overhead entirely and are a cheap, reliable performance win for GET endpoints that never call SaveChanges.

Never run dotnet ef database update directly against a production database as part of application startup code. Apply migrations through a controlled deployment step (or a dedicated migration job) so a bad migration can't take down every replica of your app simultaneously.

Querying and SaveChanges

LINQ queries against a DbSet<T> are translated to SQL only when enumerated — calling ToListAsync, FirstOrDefaultAsync, or iterating in a foreach triggers translation and execution, while the query itself remains an IQueryable expression tree until then. This deferred execution lets you compose filters, includes, and projections across multiple method calls before EF Core generates a single SQL statement. Include() and ThenInclude() eagerly load related entities to avoid N+1 query problems, while SaveChangesAsync() flushes every tracked insert, update, and delete accumulated on the context as a single transaction.

🏏

Cricket analogy: Deferred execution is like a captain setting the field before the ball is bowled — the plan (IQueryable expression) is fully composed, but it only 'executes' the instant the bowler releases the delivery (you call ToListAsync).

  • AddDbContext registers DbContext with a scoped lifetime, matching EF Core's per-request unit-of-work design.
  • Provider selection (UseSqlServer, UseNpgsql, etc.) is configured inside the AddDbContext options lambda.
  • Migrations generated via dotnet ef migrations add are versioned C# files with reversible Up/Down methods.
  • Apply migrations through a controlled deployment step, never automatically on every app startup in production.
  • LINQ queries use deferred execution — SQL is generated only when the query is enumerated.
  • Use AsNoTracking() for read-only queries and Include()/ThenInclude() to avoid N+1 query problems.
  • SaveChangesAsync() commits all tracked changes on the context as a single database transaction.

Practice what you learned

Was this page helpful?

Topics covered

#ASPNETCoreStudyNotes#MicrosoftTechnologies#EntityFrameworkCoreIntegration#Entity#Framework#Core#Integration#StudyNotes#SkillVeris#ExamPrep