Skip to main content

Command Palette

Search for a command to run...

AddDbContext vs AddDbContextPool vs AddDbContextFactory in ASP.NET Core: Which to Use in 2026?

Updated
โ€ข11 min read
AddDbContext vs AddDbContextPool vs AddDbContextFactory in ASP.NET Core: Which to Use in 2026?

Three Ways to Register DbContext โ€” and Why the Choice Matters

Every ASP.NET Core application that uses Entity Framework Core has to answer the same registration question at startup: AddDbContext, AddDbContextPool, or AddDbContextFactory? At first glance, these three methods appear to do the same thing โ€” make a DbContext available through dependency injection. In practice, they have different lifetime semantics, different performance characteristics, and different failure modes. Picking the wrong one for your scenario won't always cause an obvious error; it can silently introduce state leakage, unnecessary object allocation, or subtle bugs in background services that surface only under load.

The deeper picture โ€” including how DbContext registration fits into a production EF Core setup alongside repository patterns, AsNoTracking, and migration strategies โ€” is covered in Chapter 3 of the Zero to Production course, where each piece is wired into a real, running API codebase from the ground up.

ASP.NET Core Web API: Zero to Production

If you want the full implementation โ€” annotated, production-ready, and tested โ€” including the exact registration patterns used in a real enterprise API, Patreon has the complete source code with every trade-off already worked through.


What Each Registration Method Actually Does

Before comparing them, it helps to understand what each method does under the hood โ€” not just what Microsoft's documentation says, but what actually happens to DbContext instances at runtime.

AddDbContext

AddDbContext<TContext> registers your context with a scoped lifetime by default. That means one DbContext instance is created per HTTP request (or per DI scope) and disposed when the scope ends. This is the ASP.NET Core project template default โ€” the one you get when you run dotnet new webapi.

The key characteristics:

  • A fresh instance is created for every request scope

  • Any OnConfiguring or constructor-injected services are initialized per instance

  • There is no instance reuse between requests

  • Stateful configuration (injecting scoped services into the context) is fully supported

  • Thread-safe by nature: one context per scope, one scope per request

AddDbContextPool

AddDbContextPool<TContext> was introduced in EF Core 2.0 as a performance optimization. Instead of creating and disposing a DbContext for every request, it maintains a pool of reusable instances. When a scope ends and its context is "disposed", EF Core resets the context state and returns the instance to the pool. The next request that needs a context gets a pre-warmed instance from the pool rather than constructing a new one.

The key characteristics:

  • Instances are recycled across scopes; construction cost is paid once

  • The pool has a configurable size (default: 1,024 instances)

  • Because instances are recycled, your DbContext must be stateless โ€” no constructor-injected scoped services, no instance-level mutable state

  • EF Core resets change tracking, query filters, and transaction state between uses, but it does not re-run OnConfiguring

  • IDbContextPooledObjectPolicy<T> is available if you need custom reset logic

AddDbContextFactory

AddDbContextFactory<TContext> registers an IDbContextFactory<TContext> rather than a DbContext directly. The factory is registered as a singleton and creates short-lived DbContext instances on demand โ€” meaning they are created and disposed inside the consuming code, not managed by the DI scope.

The key characteristics:

  • No scope dependency: the factory is a singleton; callers control the DbContext lifetime explicitly

  • Perfect for background services, Blazor components, and long-lived singleton services that need to perform database work

  • Each CreateDbContext() call produces a fresh, independent instance

  • Combines cleanly with AddDbContextPool โ€” if both are registered, the factory draws instances from the pool


Side-by-Side Comparison

Dimension AddDbContext AddDbContextPool AddDbContextFactory
DI Lifetime Scoped Scoped (pooled) Singleton (factory)
Instance Reuse โŒ One per scope โœ… Pooled, recycled Caller-controlled
Stateful Config โœ… Supported โŒ Not supported โœ… Supported
Scoped Service Injection โœ… Fully supported โŒ Not allowed โŒ Not recommended
Background Services โš ๏ธ Needs IServiceScopeFactory โš ๏ธ Needs IServiceScopeFactory โœ… Native fit
Blazor Server โš ๏ธ Scope mismatch risk โš ๏ธ Scope mismatch risk โœ… Native fit
Performance Baseline Higher (allocation savings) Baseline
Complexity Low Medium Medium

When to Use AddDbContext

AddDbContext is the right choice for most ASP.NET Core Web API and MVC applications. It is simple, predictable, and fully supports any configuration approach โ€” including injecting scoped services like ICurrentUserService or ITenantProvider directly into the context constructor.

Use it when:

  • Your application follows a standard request/response model

  • You inject scoped services into your DbContext (e.g., for row-level security via global query filters)

  • You are using the repository pattern and each repository is also scoped

  • You have not profiled a performance bottleneck caused by DbContext instantiation

Do not use it as a shortcut inside background services or Blazor Server components. Injecting a scoped DbContext into a long-lived singleton service will cause an ObjectDisposedException or stale tracking state โ€” often silently.


When to Use AddDbContextPool

AddDbContextPool is worth enabling for high-throughput APIs where profiling shows measurable overhead from repeated DbContext instantiation. The performance gain comes from avoiding the cost of constructing a new instance, wiring up internal services, and running OnConfiguring for every request.

Use it when:

  • Your application handles a large volume of short-lived, stateless requests

  • Your DbContext does not need scoped service injection

  • You have validated (through benchmarks or profiling) that context allocation is a bottleneck

  • Your context configuration is effectively constant across the application lifetime

The most common mistake with pooled contexts is attempting to inject scoped services through the constructor. Because pooled instances are recycled, their constructor does not re-run between uses โ€” which means a ICurrentUserService injected at construction time would carry the first user's identity into subsequent requests. This is a security-critical bug, not just a correctness issue. If you need per-request context-state customization with a pooled context, use IDbContextPooledObjectPolicy<T> to reset that state on each recycle.


When to Use AddDbContextFactory

AddDbContextFactory is the right choice when the caller needs to control the DbContext lifetime independently of the DI scope. The two primary scenarios are background services and Blazor Server components.

Background Services

A BackgroundService or IHostedService is a singleton. Injecting a scoped DbContext into a singleton is a classic mistake โ€” the context is shared across multiple asynchronous operations, violating EF Core's thread-safety requirement. The traditional fix is to inject IServiceScopeFactory and create a scope per unit of work. With IDbContextFactory<T>, you skip the scope ceremony entirely: create a context, do the work, dispose it.

Blazor Server

Blazor Server components have a lifetime that spans multiple renders, which does not map cleanly to ASP.NET Core's request scope. A scoped DbContext outlives its intended lifetime in this model. IDbContextFactory<T> gives each render or event handler its own short-lived context without scope coupling.

Combining with Pooling

AddDbContextFactory and AddDbContextPool are not mutually exclusive. When both are registered, the factory draws from the pool. This means you get the explicit lifetime control of the factory pattern with the allocation efficiency of pooling โ€” the best of both approaches for high-throughput background processing.


Does EF Core DbContext Registration Affect Query Behavior?

Yes โ€” in one important way. The registration method determines how long change tracking persists. With AddDbContext (scoped), change tracking accumulates across the entire request, which is intentional for unit-of-work style commits. With AddDbContextFactory, each context instance starts with a clean tracker.

For read-heavy endpoints, this distinction matters less because AsNoTracking bypasses the change tracker entirely regardless of how the context is registered. The registration choice primarily affects write patterns and long-lived scenarios.


The Stale DbContext Problem in Background Services

One of the most common production issues with EF Core registration is the stale DbContext problem. It manifests when a background service holds a reference to a DbContext across multiple units of work. Because the context accumulates tracked entities and query filter state over time, long-running operations gradually degrade: queries slow down, tracked entity counts climb, and memory usage grows.

The correct pattern in a background service โ€” whether using IServiceScopeFactory or IDbContextFactory<T> โ€” is to treat each unit of work as a short-lived context. Create it, do the work, dispose it. This pairs directly with the recommendations in EF Core Connection Pool Exhaustion in ASP.NET Core: Root Cause and Fix โ€” pooling at the ADO.NET level and pooling at the DbContext level are complementary, not competing, strategies.


Decision Matrix

Scenario Recommended Registration
Standard Web API / MVC application AddDbContext
High-throughput API, stateless context AddDbContextPool
Background service / Worker Service AddDbContextFactory
Blazor Server component AddDbContextFactory
Context needs scoped service injection AddDbContext
Row-level security via global query filters AddDbContext
High-throughput + explicit lifetime control AddDbContextFactory + AddDbContextPool

Anti-Patterns to Avoid

Injecting a pooled context that holds scoped state. If you use AddDbContextPool and inject anything scoped or request-specific into the context constructor โ€” user identity, tenant resolvers, ambient state โ€” you will get state leakage across requests. This is the most common pooling mistake in production codebases.

Using AddDbContext in a singleton BackgroundService. This forces you to resolve the scoped DbContext from a singleton, which EF Core will refuse at runtime with InvalidOperationException: Cannot consume scoped service 'AppDbContext' from singleton. Use IServiceScopeFactory or IDbContextFactory<T> instead.

Never disposing factory-created contexts. When you call factory.CreateDbContext(), you own the context lifetime. If you do not dispose it โ€” via using or explicit Dispose() โ€” connections remain open indefinitely, and your application will exhaust the underlying connection pool under load.

Switching from AddDbContext to AddDbContextPool without a stateless audit. Many codebases have subtle per-request state hidden in a DbContext subclass โ€” overridden SaveChangesAsync that stamps audit fields using injected services, for example. Switching to pooled registration silently breaks these patterns because the constructor does not re-run.


FAQ

What is the difference between AddDbContext and AddDbContextPool in EF Core?

AddDbContext creates a new DbContext instance for each request scope and disposes it when the scope ends. AddDbContextPool recycles instances from an internal pool, avoiding repeated construction costs. The trade-off is that pooled contexts cannot hold scoped service state โ€” the context must be stateless between uses.

Does AddDbContextPool improve performance significantly?

For most mid-traffic applications, the difference is negligible. AddDbContextPool produces measurable throughput gains at high request volumes โ€” typically hundreds of requests per second per instance โ€” where object allocation and initialization overhead becomes a bottleneck. Always benchmark your specific workload before switching.

Can I use AddDbContextFactory with AddDbContextPool at the same time?

Yes. When both are registered, the IDbContextFactory<T> draws instances from the pool. This lets you combine the explicit lifetime control of the factory pattern with the allocation efficiency of pooling โ€” particularly useful for high-throughput background processors.

Why can't I inject scoped services into a pooled DbContext?

Because pooled DbContext instances are reused across multiple requests. The constructor only runs once โ€” when the instance is first created. Any scoped service injected at construction time retains the state from the first request that created the instance. All subsequent requests share that stale reference, which is incorrect and potentially a serious security issue.

Should I use AddDbContextFactory for all background services in ASP.NET Core?

For background services that access the database, AddDbContextFactory is the cleanest approach. It eliminates scope creation boilerplate (IServiceScopeFactory), makes lifetime explicit, and works correctly with both pooling and non-pooling registration. The alternative โ€” creating a IServiceScope inside the background service โ€” is valid but adds indirection.

What happens to tracked entities when a pooled DbContext is returned to the pool?

EF Core automatically resets the change tracker and clears any local entity sets when a pooled context is returned. You do not need to manually call ChangeTracker.Clear(). However, state that bypasses EF Core's reset mechanism โ€” such as custom properties on your DbContext subclass โ€” will not be cleared unless you implement IDbContextPooledObjectPolicy<T>.

Is DbContext registration relevant for integration tests in ASP.NET Core?

Yes. Integration tests using WebApplicationFactory typically replace the production DbContext registration with an in-memory or SQLite provider. If the production code uses AddDbContextPool, the test replacement must match the expected interface โ€” injecting TContext directly will still work, but if production code resolves IDbContextFactory<T>, the test setup must also register the factory.

More from this blog

C

Coding Droplets

248 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.