Configuring DbContext and Migrations in Entity Framework Core

The Foundation of Database Interaction and Evolution

In Entity Framework (EF) Core, the DbContext class is the heart of your data access layer – managing connections, tracking entities, and performing database operations. Migrations, on the other hand, provide a structured way to evolve your database schema as your model changes.

Together, these two components make Code First development powerful, flexible, and production-ready.


πŸ”Ή What Is a DbContext?

The DbContext is a lightweight, central class that:

  • Manages entity objects during runtime.
  • Handles querying and saving data to the database.
  • Tracks changes and maintains relationships.
  • Bridges your C# model and the database.

You typically create one custom context per domain or database.


πŸ”Ή Creating a DbContext

Here’s a simple example of a custom DbContext:

using Microsoft.EntityFrameworkCore;

public class SchoolContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer("Server=.;Database=SchoolDB;Trusted_Connection=True;");
    }
}

βœ… The DbSet<TEntity> properties represent database tables.
βœ… The connection string defines which database EF Core connects to.


πŸ”Ή Registering DbContext (Dependency Injection)

In ASP.NET Core or other DI-enabled environments, register your context in Program.cs or Startup.cs:

builder.Services.AddDbContext<SchoolContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolDB")));

And in appsettings.json:

{
  "ConnectionStrings": {
    "SchoolDB": "Server=.;Database=SchoolDB;Trusted_Connection=True;"
  }
}

βœ… This approach enables clean dependency injection and testability.
βœ… The same context configuration can be reused across your project.


πŸ”Ή Overriding OnModelCreating

The OnModelCreating method allows fine-tuned model configuration using the Fluent API.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>()
        .Property(s => s.Name)
        .IsRequired()
        .HasMaxLength(100);

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Students)
        .WithMany(s => s.Courses)
        .UsingEntity(j => j.ToTable("StudentCourses"));
}

βœ… Define relationships, keys, table names, constraints, and indexes programmatically.
βœ… Use this for configurations not expressible through Data Annotations.


πŸ”Ή Example Model Setup

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Course> Courses { get; set; } = new List<Course>();
}

public class Course
{
    public int Id { get; set; }
    public string Title { get; set; }
    public virtual ICollection<Student> Students { get; set; } = new List<Student>();
}

πŸ”Ή Configuring Database Providers

Entity Framework Core supports multiple providers:

DatabaseExample Configuration
SQL Serveroptions.UseSqlServer("...")
SQLiteoptions.UseSqlite("Data Source=app.db")
PostgreSQLoptions.UseNpgsql("Host=localhost;Database=...;Username=...;Password=...")
MySQLoptions.UseMySql("Server=localhost;Database=...;User=...;Password=...")
In-Memory (Testing)options.UseInMemoryDatabase("TestDB")

βœ… Choose your provider based on your deployment or testing needs.


πŸ”Ή Understanding Migrations

Migrations are EF Core’s way of applying incremental schema changes as your model evolves.

When your model changes (e.g., a new property or entity), EF can compare your C# model to the existing database schema and create migration scripts automatically.


πŸ”Ή Creating and Applying Migrations

Run these commands in the Package Manager Console or terminal:

1️⃣ Add your first migration:

Add-Migration InitialCreate

Creates a Migrations folder with:

  • A migration class (InitialCreate.cs)
  • A snapshot of your model

2️⃣ Apply migration to the database:

Update-Database

EF generates and executes the SQL commands to create or update your database schema.


πŸ”Ή Viewing Generated SQL

EF Core can show the SQL it generates β€” useful for debugging or performance analysis:

options.UseSqlServer("connection_string")
       .LogTo(Console.WriteLine, LogLevel.Information);

βœ… This prints SQL queries and migration commands to the console during runtime.


πŸ”Ή Updating Migrations Over Time

When you modify models (add/remove properties or entities):

Add-Migration AddTeacherTable
Update-Database

To rollback or revert to a previous version:

Update-Database PreviousMigrationName

To remove the last migration (not yet applied):

Remove-Migration

πŸ”Ή Customizing Migration Output

Migration files are fully editable C# classes β€” you can modify them to adjust table names, add indexes, or seed data.

Example snippet from a migration class:

migrationBuilder.CreateTable(
    name: "Students",
    columns: table => new
    {
        Id = table.Column<int>(nullable: false)
            .Annotation("SqlServer:Identity", "1, 1"),
        Name = table.Column<string>(maxLength: 100, nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_Students", x => x.Id);
    });

βœ… Migrations are version-controlled, ensuring predictable deployments.


πŸ”Ή Seeding Data in Migrations

You can seed initial data using HasData() in OnModelCreating:

modelBuilder.Entity<Course>().HasData(
    new Course { Id = 1, Title = "Mathematics" },
    new Course { Id = 2, Title = "History" }
);

When a migration runs, this data is inserted if not already present.


πŸ”Ή Best Practices

  • Use dependency injection for DbContext in multi-layered apps.
  • Keep one migration per logical change β€” small and descriptive.
  • Never edit generated migrations manually unless you know what you’re doing.
  • Always check migration SQL in staging before deploying to production.
  • Use InMemoryDatabase for unit tests to avoid external dependencies.

πŸ”Ή Common Pitfalls

⚠ Changing entity names after initial migration β†’ may cause EF to drop and recreate tables (potential data loss).
⚠ Multiple contexts pointing to the same database β†’ ensure consistent configuration.
⚠ Forgetting Update-Database β†’ model changes won’t reflect in DB.


πŸ§ͺ Challenge Task

  1. Create a new LibraryContext with Book and Author entities.
  2. Configure the relationship: one Author β†’ many Books.
  3. Set up OnModelCreating using Fluent API to limit Book.Title to 150 characters.
  4. Create and apply migrations:
    • Add-Migration InitialLibrary
    • Update-Database
  5. Add seeding data for a default Author and a Book.

Expected Database Result:

Authors
-------
Id | Name
1  | Jane Austen

Books
-------
Id | Title              | AuthorId
1  | Pride and Prejudice | 1

πŸ‘¨β€πŸ’» Want More?

Our C# Deep Dive Course covers:

  • Configuring DbContext for multi-environment setups
  • Connection resiliency and performance tuning
  • Migrations in team environments and CI/CD pipelines
  • Schema versioning and rollback strategies
  • Advanced seeding and data protection techniques

Mastering DbContext and Migrations means mastering the bridge between your C# models and a living, evolving database β€” ensuring your applications scale cleanly, safely, and efficiently.