# 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](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics)

### The subtle trap

Imagine your context contains this property:

```csharp
public int TenantId { get; set; }
```

And your global query filter uses it:

```csharp
modelBuilder.Entity<Order>()
    .HasQueryFilter(order => order.TenantId == TenantId);
```

Now consider this sequence:

1. Tenant A’s request receives a context with `TenantId = 101`.
2. The request finishes, and the context returns to the pool.
3. Tenant B’s request receives that same context instance.
4. 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](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics)

### 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](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics)

EF Core resets its internal context state. That does not mean it reinitializes your custom tenant property for the next caller. [learn.microsoft](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics)

### 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:

```csharp
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](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics)

### 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](https://learn.microsoft.com/en-us/ef/core/performance/advanced-performance-topics)

#DotNet #EFCore #AspNetCore #MultiTenancy #SoftwareEngineering
