diff --git a/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs b/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs index 255b01a2737..67cfd5cf65d 100644 --- a/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs @@ -1028,6 +1028,83 @@ public static IServiceCollection AddPooledDbContextFactory return serviceCollection; } + /// + /// Registers a pooled in the + /// for creating instances of the specified + /// type. + /// + /// + /// + /// This parameterless overload aligns the pooled factory with the centralized + /// configuration model introduced by + /// + /// When used together, options (including the database provider) configured in + /// ConfigureDbContext<TContext> automatically flow into the pooled factory, + /// avoiding redundant configuration lambdas. + /// + /// + /// See Using DbContext with dependency injection, + /// Using DbContext factories, and + /// Using DbContext pooling + /// for more information and examples. + /// + /// + /// + /// The type of to be created by the factory. + /// + /// + /// The to which the services are added. + /// + /// + /// The same so that multiple calls can be chained. + /// + public static IServiceCollection AddPooledDbContextFactory + <[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>( + this IServiceCollection serviceCollection) + where TContext : DbContext + => AddPooledDbContextFactory( + serviceCollection, + static (_, __) => { }, + DbContextPool.DefaultPoolSize); + + /// + /// Registers a pooled in the + /// for creating instances of the specified + /// type, using a custom pool size. + /// + /// + /// + /// This overload aligns with the EF Core centralized configuration model + /// + /// and allows specifying a custom . + /// Options configured via ConfigureDbContext<TContext> are automatically used, + /// eliminating the need to repeat provider configuration. + /// + /// + /// + /// The type of to be created by the factory. + /// + /// + /// The to which the services are added. + /// + /// + /// The maximum number of instances retained by the pool. + /// The default is 1024. + /// + /// + /// The same so that multiple calls can be chained. + /// + public static IServiceCollection AddPooledDbContextFactory + <[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>( + this IServiceCollection serviceCollection, + int poolSize) + where TContext : DbContext + => AddPooledDbContextFactory( + serviceCollection, + static (_, __) => { }, + poolSize); + + /// /// Configures the given context type in the . /// diff --git a/test/EFCore.Tests/Infrastructure/AddPooledDbContextFactoryParameterlessTest.cs b/test/EFCore.Tests/Infrastructure/AddPooledDbContextFactoryParameterlessTest.cs new file mode 100644 index 00000000000..f3d84f377dd --- /dev/null +++ b/test/EFCore.Tests/Infrastructure/AddPooledDbContextFactoryParameterlessTest.cs @@ -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 options) : base(options) { } + } + + [Fact] + public async Task Parameterless_factory_should_use_ConfigureDbContext_options() + { + var services = new ServiceCollection(); + + services.ConfigureDbContext((sp, opts) => + opts.UseInMemoryDatabase("test_db")); + + services.AddPooledDbContextFactory(); + + using var provider = services.BuildServiceProvider(); + var factory = provider.GetRequiredService>(); + 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((sp, opts) => + opts.UseInMemoryDatabase("test_db_custom_pool")); + + services.AddPooledDbContextFactory(poolSize: 256); + + using var provider = services.BuildServiceProvider(); + var factory = provider.GetRequiredService>(); + 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((sp, opts) => + opts.UseInMemoryDatabase("scoped_db")); + + services.AddPooledDbContextFactory(); + + using var provider = services.BuildServiceProvider(); + + using var scope = provider.CreateScope(); + var ctx = scope.ServiceProvider.GetRequiredService(); + Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", ctx.Database.ProviderName); + } + + [Fact] + public void Pooled_services_are_registered_and_singleton() + { + var services = new ServiceCollection(); + + services.ConfigureDbContext((sp, opts) => + opts.UseInMemoryDatabase("pool_reg_db")); + + services.AddPooledDbContextFactory(); + + using var provider = services.BuildServiceProvider(); + + var pool1 = provider.GetRequiredService>(); + var pool2 = provider.GetRequiredService>(); + + // Should be the same singleton instance + Assert.Same(pool1, pool2); + + // And the factory should resolve + var factory = provider.GetRequiredService>(); + 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(); + + using var provider = services.BuildServiceProvider(); + var factory = provider.GetRequiredService>(); + await using var db = await factory.CreateDbContextAsync(); + + // Trigger provider requirement (any DB operation works; EnsureCreated is simple & provider-agnostic) + await Assert.ThrowsAsync(async () => + { + await db.Database.EnsureCreatedAsync(); + }); + } +}