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.
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.
SaveChanges().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);
}
}
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);
}
});
How long should a DbContext live? The answer dictates your application's architecture.
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.
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().
One instance for the entire application lifetime. Disastrous. DbContext is not thread-safe. Concurrent requests will crash the application.
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)
);
OnModelCreating.IHostedService) are Singletons. If you inject a Scoped DbContext into them, the app crashes. Instead, use IDbContextFactory<T> to spawn contexts on demand.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."