Tutorials ASP.NET Core MVC Mastery
DbContext
On this page
DbContext in EF Core — The Heart of Data Access
In Entity Framework Core, the DbContext is the absolute center of your data universe. It represents a session with the database, handling database connections, querying, change tracking, and saving data. Understanding its architecture and lifecycle is the difference between a high-performance application and one plagued by memory leaks.
1. WHAT is DbContext?
A DbContext is a combination of the Unit of Work and Repository patterns. It acts as a bridge between your C# entity classes and the relational database.
- Unit of Work: Tracks changes made to entities during the lifetime of the context and commits them all in a single transaction via
SaveChanges(). - Repository (DbSet): Exposes `DbSet<TEntity>` properties, which act as collections of your entities, allowing you to query them using LINQ.
2. REAL-TIME PRODUCTION EXAMPLE
Step 1: Creating the ApplicationDbContext
Never use the base DbContext directly. Always inherit from it to create your domain-specific context.
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
// The constructor accepts options (connection strings, providers)
// passed down from Dependency Injection in Program.cs
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
// 1. Define Repositories (DbSets)
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
// 2. Configure Schema using the Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Instead of polluting entities with [Attributes],
// enterprise apps use Fluent API for strict control.
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.Property(e => e.Price).HasPrecision(18, 2); // Extremely important for money!
});
// Best Practice: Moving configurations into separate classes
// modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
}
Step 2: Registration in Program.cs
In ASP.NET Core, the DI container manages the lifecycle of the DbContext.
// Read connection string from appsettings.json
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// Register as Scoped by default
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
// Configure the database provider (SQL Server, PostgreSQL, etc.)
options.UseSqlServer(connectionString, sqlOptions =>
{
// Enterprise setting: Automatically retry on transient network failures
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null);
});
// Developer helper: Log SQL queries to console (dev only)
if (builder.Environment.IsDevelopment())
{
options.EnableSensitiveDataLogging();
options.LogTo(Console.WriteLine, LogLevel.Information);
}
});
3. The DbContext Lifecycle (Critical)
How long should a DbContext live? The answer dictates your application's architecture.
Transient
A new instance is created every time it is requested. Bad idea. Change tracking breaks down because different services injected into the same request get different contexts.
Scoped (Standard)
One instance is created per HTTP request. Perfect for web apps. All services within that request share the same context and share the same transaction upon SaveChanges().
Singleton
One instance for the entire application lifetime. Disastrous. DbContext is not thread-safe. Concurrent requests will crash the application.
4. DbContextPooling (High Performance)
Creating and disposing a DbContext takes CPU cycles. If your app handles high traffic, you can "pool" contexts. When a request ends, the context is reset and returned to a pool rather than being destroyed and garbage collected.
// Instead of AddDbContext, use AddDbContextPool
builder.Services.AddDbContextPool<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString)
);
5. Best Practices & Pitfalls
- Use Fluent API over Data Annotations: Keep your domain models pure. Place database-specific configurations (like composite keys or index strategies) in
OnModelCreating. - Don't inject DbContext into Singletons: Background services (
IHostedService) are Singletons. If you inject a ScopedDbContextinto them, the app crashes. Instead, useIDbContextFactory<T>to spawn contexts on demand. - Call SaveChangesAsync() ONCE: The DbContext tracks multiple inserts/updates. Don't call SaveChanges in a loop. Do the loop, then call SaveChanges once to batch the SQL queries into a single transaction.
6. Interview Mastery
Q: "Why would you use IDbContextFactory instead of standard Dependency Injection?"
Architect Answer: "Standard DI registers DbContext as a Scoped service, meaning it is tied to the lifespan of an HTTP Request. But what if there is no HTTP request? In background workers (like a RabbitMQ consumer or an `IHostedService`), the service is a Singleton. Injecting a Scoped DbContext into a Singleton is an anti-pattern that creates a memory leak and concurrency crashes because DbContext isn't thread-safe. In those scenarios, I inject `IDbContextFactory`. The background service uses the factory to manually spawn `using (var context = _factory.CreateDbContext())` — performing its work in a safe, isolated, and properly disposed context."