Your .NET app can handle a new request with an old tenant’s state.

You replace AddDbContext with AddDbContextPool to reduce context-creation overhead.
A small performance improvement. What could go wrong?
In a multi-tenant application, the answer is surprisingly serious: a reused context can carry tenant-specific state into another request if you don’t explicitly refresh it. learn.microsoft
The subtle trap
Imagine your context contains this property:
public int TenantId { get; set; }
And your global query filter uses it:
modelBuilder.Entity<Order>()
.HasQueryFilter(order => order.TenantId == TenantId);
Now consider this sequence:
- Tenant A’s request receives a context with
TenantId = 101. - The request finishes, and the context returns to the pool.
- Tenant B’s request receives that same context instance.
- Your application fails to overwrite
TenantId.
The filter can now use Tenant A’s ID while serving Tenant B.
Pooling isn’t automatically unsafe. The mistake is assuming a reused context has fresh application-defined state. Microsoft explicitly calls out tenant IDs as state that needs special handling with pooled contexts. learn.microsoft
Why the obvious fix doesn’t work
“I’ll set the tenant in OnConfiguring.”
With context pooling, OnConfiguring runs only when that context instance is first created—not every time it is borrowed from the pool. It is therefore the wrong place for state that must change between requests. learn.microsoft
EF Core resets its internal context state. That does not mean it reinitializes your custom tenant property for the next caller. learn.microsoft
The safer pattern
Use a scoped factory that:
- Resolves the tenant from trusted authentication data.
- Obtains a context from the pooled factory.
- Assigns the current tenant ID before returning the context.
The essential step looks like this:
public AppDbContext CreateDbContext()
{
var context = _pooledFactory.CreateDbContext();
// Validated tenant identity for this scope.
context.TenantId = _tenantId;
return context;
}
Microsoft documents this scoped-factory approach for multi-tenant applications using context pooling. Don’t accept an arbitrary client-supplied tenant ID as proof of authorization. learn.microsoft
The engineering lesson
Reuse changes the lifecycle assumptions your code can safely make.
A new request does not necessarily mean a new object.
Before enabling pooling, audit every piece of request-specific state attached to your context—not just the database queries. And remember: DbContext pooling and database connection pooling are separate mechanisms. You don’t need the former to benefit from the latter. learn.microsoft
#DotNet #EFCore #AspNetCore #MultiTenancy #SoftwareEngineering
Comments
Join the discussionNo comments yet. Be the first to comment.



