Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,83 @@ public static IServiceCollection AddPooledDbContextFactory
return serviceCollection;
}

/// <summary>
/// Registers a pooled <see cref="IDbContextFactory{TContext}" /> in the
/// <see cref="IServiceCollection" /> for creating instances of the specified
/// <see cref="DbContext" /> type.
/// </summary>
/// <remarks>
/// <para>
/// This parameterless overload aligns the pooled factory with the centralized
/// configuration model introduced by
/// <see cref="ConfigureDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}, ServiceLifetime)"/>
/// When used together, options (including the database provider) configured in
/// <c>ConfigureDbContext&lt;TContext&gt;</c> automatically flow into the pooled factory,
/// avoiding redundant configuration lambdas.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-di">Using DbContext with dependency injection</see>,
/// <see href="https://aka.ms/efcore-docs-dbcontext-factory">Using DbContext factories</see>, and
/// <see href="https://aka.ms/efcore-docs-dbcontext-pooling">Using DbContext pooling</see>
/// for more information and examples.
/// </para>
/// </remarks>
/// <typeparam name="TContext">
/// The type of <see cref="DbContext" /> to be created by the factory.
/// </typeparam>
/// <param name="serviceCollection">
/// The <see cref="IServiceCollection" /> to which the services are added.
/// </param>
/// <returns>
/// The same <see cref="IServiceCollection" /> so that multiple calls can be chained.
/// </returns>
public static IServiceCollection AddPooledDbContextFactory
<[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>(
this IServiceCollection serviceCollection)
where TContext : DbContext
=> AddPooledDbContextFactory<TContext>(
serviceCollection,
static (_, __) => { },
DbContextPool<DbContext>.DefaultPoolSize);

/// <summary>
/// Registers a pooled <see cref="IDbContextFactory{TContext}" /> in the
/// <see cref="IServiceCollection" /> for creating instances of the specified
/// <see cref="DbContext" /> type, using a custom pool size.
/// </summary>
/// <remarks>
/// <para>
/// This overload aligns with the EF Core centralized configuration model
/// <see cref="ConfigureDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}, ServiceLifetime)" />
/// and allows specifying a custom <paramref name="poolSize"/>.
/// Options configured via <c>ConfigureDbContext&lt;TContext&gt;</c> are automatically used,
/// eliminating the need to repeat provider configuration.
/// </para>
/// </remarks>
/// <typeparam name="TContext">
/// The type of <see cref="DbContext" /> to be created by the factory.
/// </typeparam>
/// <param name="serviceCollection">
/// The <see cref="IServiceCollection" /> to which the services are added.
/// </param>
/// <param name="poolSize">
/// The maximum number of <typeparamref name="TContext" /> instances retained by the pool.
/// The default is 1024.
/// </param>
/// <returns>
/// The same <see cref="IServiceCollection" /> so that multiple calls can be chained.
/// </returns>
public static IServiceCollection AddPooledDbContextFactory
<[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>(
this IServiceCollection serviceCollection,
int poolSize)
where TContext : DbContext
=> AddPooledDbContextFactory<TContext>(
serviceCollection,
static (_, __) => { },
poolSize);


/// <summary>
/// Configures the given context type in the <see cref="IServiceCollection" />.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Internal;

namespace Microsoft.EntityFrameworkCore.Infrastructure;

public class AddPooledDbContextFactoryParameterlessTest
{
private sealed class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options) { }
}

[Fact]
public async Task Parameterless_factory_should_use_ConfigureDbContext_options()
{
var services = new ServiceCollection();

services.ConfigureDbContext<TestDbContext>((sp, opts) =>
opts.UseInMemoryDatabase("test_db"));

services.AddPooledDbContextFactory<TestDbContext>();

using var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
await using var db = await factory.CreateDbContextAsync();

Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", db.Database.ProviderName);
}

[Fact]
public async Task Parameterless_factory_with_custom_pool_size_should_still_resolve()
{
var services = new ServiceCollection();

services.ConfigureDbContext<TestDbContext>((sp, opts) =>
opts.UseInMemoryDatabase("test_db_custom_pool"));

services.AddPooledDbContextFactory<TestDbContext>(poolSize: 256);

using var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
await using var db = await factory.CreateDbContextAsync();

Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", db.Database.ProviderName);
}

[Fact]
public void Scoped_resolution_of_TContext_uses_pooled_factory()
{
var services = new ServiceCollection();

services.ConfigureDbContext<TestDbContext>((sp, opts) =>
opts.UseInMemoryDatabase("scoped_db"));

services.AddPooledDbContextFactory<TestDbContext>();

using var provider = services.BuildServiceProvider();

using var scope = provider.CreateScope();
var ctx = scope.ServiceProvider.GetRequiredService<TestDbContext>();
Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", ctx.Database.ProviderName);
}

[Fact]
public void Pooled_services_are_registered_and_singleton()
{
var services = new ServiceCollection();

services.ConfigureDbContext<TestDbContext>((sp, opts) =>
opts.UseInMemoryDatabase("pool_reg_db"));

services.AddPooledDbContextFactory<TestDbContext>();

using var provider = services.BuildServiceProvider();

var pool1 = provider.GetRequiredService<IDbContextPool<TestDbContext>>();
var pool2 = provider.GetRequiredService<IDbContextPool<TestDbContext>>();

// Should be the same singleton instance
Assert.Same(pool1, pool2);

// And the factory should resolve
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
Assert.NotNull(factory);
}

[Fact]
public async Task Parameterless_factory_without_configuration_throws_meaningful_error()
{
var services = new ServiceCollection();

// No ConfigureDbContext here on purpose.
services.AddPooledDbContextFactory<TestDbContext>();

using var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
await using var db = await factory.CreateDbContextAsync();

// Trigger provider requirement (any DB operation works; EnsureCreated is simple & provider-agnostic)
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await db.Database.EnsureCreatedAsync();
});
}
}
Loading