Interview question
Scope tenant state around a pooled DbContext
Wraps a pooled DbContext factory so every request assigns tenant state before a global query filter can run.
TL;DR
Wraps a pooled DbContext factory so every request assigns tenant state before a global query filter can run.
Context pooling, mutable request state, scoped factories, global filters, fail-closed defaults, and tenant-isolation tests.
Practice the problem like a real interview: restate, reason, implement, and test.
AppDbContext has a global filter on TenantId and is created by AddPooledDbContextFactory. Tenant identity comes from a scoped ITenantAccessor. Never let request handlers use the pooled factory directly; expose a scoped wrapper that assigns a valid tenant id on every context lease.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.
A context used for tenant A returns to the pool. A later tenant-B request leases it, and the scoped wrapper overwrites TenantId with B before any query executes. Missing tenant identity fails instead of reusing A.
DbContext pooling reuses the context instance, so OnConfiguring does not run once per request and custom mutable state needs explicit handling. I wrap the pooled factory in a scoped factory that resolves trusted tenant identity and sets it before returning the context. All tenant-aware consumers use the wrapper, making a missing assignment difficult to bypass.
public sealed class AppDbContext : DbContext
{
public Guid TenantId { get; set; } = Guid.Empty;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasQueryFilter(order =>
TenantId != Guid.Empty &&
order.TenantId == TenantId);
}
}
public sealed class TenantDbContextFactory(
IDbContextFactory<AppDbContext> pooledFactory,
ITenantAccessor tenantAccessor)
{
public async Task<AppDbContext> CreateAsync(
CancellationToken cancellationToken)
{
var tenantId = tenantAccessor.RequiredTenantId;
if (tenantId == Guid.Empty)
throw new InvalidOperationException("A tenant is required.");
var db = await pooledFactory.CreateDbContextAsync(cancellationToken);
db.TenantId = tenantId;
return db;
}
}
// Registration: handlers receive TenantDbContextFactory, not the pooled factory.
services.AddPooledDbContextFactory<AppDbContext>(ConfigureDatabase);
services.AddScoped<TenantDbContextFactory>();
Pooling reduces context setup allocations but does not make a context concurrent or remove normal disposal. The global filter parameterizes the current tenant value. Tenant-isolation tests must alternate tenants across many leased contexts and use a relational test database or container rather than the in-memory provider alone.
OnConfiguring.Spot a weak answer, missing edge case, or clearer explanation? Send it in.