Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docs for DbContext pooling with state #3822

Merged
merged 2 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions entity-framework/core/performance/advanced-performance-topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,20 @@ Context pooling works by reusing the same context instance across requests; this

A typical scenario involving context state would be a multi-tenant ASP.NET Core application, where the context instance has a *tenant ID* which is taken into account by queries (see [Global Query Filters](xref:core/querying/filters) for more details). Since the tenant ID needs to change with each web request, we need to have go through some extra steps to make it all work with context pooling.

First, register a pooling context factory as a Singleton service, as usual:
Let's assume that your application registers a scoped `ITenant` service, which wraps the tenant ID and any other tenant-related information:

[!code-csharp[Main](../../../samples/core/Performance/AspNetContextPoolingWithState/Program.cs#TenantResolution)]

As written above, pay special attention to where you get the tenant ID from: this is an important aspect of your application's security.
roji marked this conversation as resolved.
Show resolved Hide resolved

Once we have our scoped `ITenant` service, register a pooling context factory as a Singleton service, as usual:

[!code-csharp[Main](../../../samples/core/Performance/AspNetContextPoolingWithState/Program.cs#RegisterSingletonContextFactory)]

Next, write a custom context factory which gets a pooled context from the Singleton factory we registered, finds the tenant ID in the web request's `HttpContext`, and injects the ID into the context:
Next, write a custom context factory which gets a pooled context from the Singleton factory we registered, and injects the tenant ID into context instances it hands out:

[!code-csharp[Main](../../../samples/core/Performance/AspNetContextPoolingWithState/WeatherForecastScopedFactory.cs#WeatherForecastScopedFactory)]

As written above, pay special attention to where you get the tenant ID from: this is an important aspect of your application's security.

Once we have our custom context factory, register it as a Scoped service:

[!code-csharp[Main](../../../samples/core/Performance/AspNetContextPoolingWithState/Program.cs#RegisterScopedContextFactory)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Performance.AspNetContextPoolingWithState;

public interface ITenant
{
public int TenantId { get; set; }
}
20 changes: 16 additions & 4 deletions samples/core/Performance/AspNetContextPoolingWithState/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Primitives;
using Performance.AspNetContextPoolingWithState;

var builder = WebApplication.CreateBuilder(args);
Expand All @@ -8,6 +8,21 @@
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

#region TenantResolution
// Below is a minimal tenant resolution strategy, which registers a scoped ITenant service in DI.
// In this sample, we simply accept the tenant ID as a request query, which means that a client can impersonate any
// tenant. In a real application, the tenant ID would be set based on secure authentication data.
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ITenant>(sp =>
{
var tenantIdString = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request.Query["TenantId"];

return tenantIdString != StringValues.Empty && int.TryParse(tenantIdString, out var tenantId)
? new Tenant(tenantId)
: null;
});
#endregion

// First, register a pooling context factory as a Singleton service, as usual:
#region RegisterSingletonContextFactory
builder.Services.AddPooledDbContextFactory<WeatherForecastContext>(
Expand All @@ -17,7 +32,6 @@
// Register an additional context factory as a Scoped service, which gets a pooled context from the Singleton factory we registered above,
// finds the tenant ID in the web request's `HttpContext`, and injects the ID into the context:
#region RegisterScopedContextFactory
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<WeatherForecastScopedFactory>();
#endregion

Expand All @@ -36,9 +50,7 @@
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Performance.AspNetContextPoolingWithState;

public class Tenant : ITenant
{
public Tenant(int tenantId)
=> TenantId = tenantId;

public int TenantId { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,17 @@ namespace Performance.AspNetContextPoolingWithState;
#region WeatherForecastScopedFactory
public class WeatherForecastScopedFactory : IDbContextFactory<WeatherForecastContext>
{
private const int DefaultTenantId = -1;

private readonly IDbContextFactory<WeatherForecastContext> _pooledFactory;
private readonly int _tenantId;

public WeatherForecastScopedFactory(
IDbContextFactory<WeatherForecastContext> pooledFactory,
IHttpContextAccessor httpContextAccessor)
ITenant tenant)
{
_pooledFactory = pooledFactory;

// In this sample, we simply accept the tenant ID as a request query, which means that a client can impersonate any tenant.
// In a real application, the tenant ID would be set based on secure authentication data.
var tenantIdString = httpContextAccessor.HttpContext.Request.Query["TenantId"];
if (tenantIdString != StringValues.Empty && int.TryParse(tenantIdString, out var tenantId))
{
_tenantId = tenantId;
}
_tenantId = tenant?.TenantId ?? DefaultTenantId;
}

public WeatherForecastContext CreateDbContext()
Expand Down