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:
| Database | Example Configuration |
|---|---|
| SQL Server | options.UseSqlServer("...") |
| SQLite | options.UseSqlite("Data Source=app.db") |
| PostgreSQL | options.UseNpgsql("Host=localhost;Database=...;Username=...;Password=...") |
| MySQL | options.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
- Create a new
LibraryContextwithBookandAuthorentities. - Configure the relationship: one Author β many Books.
- Set up
OnModelCreatingusing Fluent API to limitBook.Titleto 150 characters. - Create and apply migrations:
Add-Migration InitialLibraryUpdate-Database
- 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.