Skip to content

Commit db591f6

Browse files
committed
feat(dbcontext): add parameterless overloads for AddPooledDbContextFactory<TContext>
Align pooled factory with EF9 ConfigureDbContext<TContext> model. Adds two parameterless overloads (default + poolSize) delegating to the existing overload. Includes tests verifying provider configuration flows via ConfigureDbContext. Fixes #34657
1 parent bfb98b4 commit db591f6

2 files changed

Lines changed: 184 additions & 0 deletions

File tree

src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,83 @@ public static IServiceCollection AddPooledDbContextFactory
10281028
return serviceCollection;
10291029
}
10301030

1031+
/// <summary>
1032+
/// Registers a pooled <see cref="IDbContextFactory{TContext}" /> in the
1033+
/// <see cref="IServiceCollection" /> for creating instances of the specified
1034+
/// <see cref="DbContext" /> type.
1035+
/// </summary>
1036+
/// <remarks>
1037+
/// <para>
1038+
/// This parameterless overload aligns the pooled factory with the centralized
1039+
/// configuration model introduced by
1040+
/// <see cref="ConfigureDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}, ServiceLifetime)"/>
1041+
/// When used together, options (including the database provider) configured in
1042+
/// <c>ConfigureDbContext&lt;TContext&gt;</c> automatically flow into the pooled factory,
1043+
/// avoiding redundant configuration lambdas.
1044+
/// </para>
1045+
/// <para>
1046+
/// See <see href="https://aka.ms/efcore-docs-di">Using DbContext with dependency injection</see>,
1047+
/// <see href="https://aka.ms/efcore-docs-dbcontext-factory">Using DbContext factories</see>, and
1048+
/// <see href="https://aka.ms/efcore-docs-dbcontext-pooling">Using DbContext pooling</see>
1049+
/// for more information and examples.
1050+
/// </para>
1051+
/// </remarks>
1052+
/// <typeparam name="TContext">
1053+
/// The type of <see cref="DbContext" /> to be created by the factory.
1054+
/// </typeparam>
1055+
/// <param name="serviceCollection">
1056+
/// The <see cref="IServiceCollection" /> to which the services are added.
1057+
/// </param>
1058+
/// <returns>
1059+
/// The same <see cref="IServiceCollection" /> so that multiple calls can be chained.
1060+
/// </returns>
1061+
public static IServiceCollection AddPooledDbContextFactory
1062+
<[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>(
1063+
this IServiceCollection serviceCollection)
1064+
where TContext : DbContext
1065+
=> AddPooledDbContextFactory<TContext>(
1066+
serviceCollection,
1067+
static (_, __) => { },
1068+
DbContextPool<DbContext>.DefaultPoolSize);
1069+
1070+
/// <summary>
1071+
/// Registers a pooled <see cref="IDbContextFactory{TContext}" /> in the
1072+
/// <see cref="IServiceCollection" /> for creating instances of the specified
1073+
/// <see cref="DbContext" /> type, using a custom pool size.
1074+
/// </summary>
1075+
/// <remarks>
1076+
/// <para>
1077+
/// This overload aligns with the EF Core centralized configuration model
1078+
/// <see cref="ConfigureDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}, ServiceLifetime)" />
1079+
/// and allows specifying a custom <paramref name="poolSize"/>.
1080+
/// Options configured via <c>ConfigureDbContext&lt;TContext&gt;</c> are automatically used,
1081+
/// eliminating the need to repeat provider configuration.
1082+
/// </para>
1083+
/// </remarks>
1084+
/// <typeparam name="TContext">
1085+
/// The type of <see cref="DbContext" /> to be created by the factory.
1086+
/// </typeparam>
1087+
/// <param name="serviceCollection">
1088+
/// The <see cref="IServiceCollection" /> to which the services are added.
1089+
/// </param>
1090+
/// <param name="poolSize">
1091+
/// The maximum number of <typeparamref name="TContext" /> instances retained by the pool.
1092+
/// The default is 1024.
1093+
/// </param>
1094+
/// <returns>
1095+
/// The same <see cref="IServiceCollection" /> so that multiple calls can be chained.
1096+
/// </returns>
1097+
public static IServiceCollection AddPooledDbContextFactory
1098+
<[DynamicallyAccessedMembers(DbContext.DynamicallyAccessedMemberTypes)] TContext>(
1099+
this IServiceCollection serviceCollection,
1100+
int poolSize)
1101+
where TContext : DbContext
1102+
=> AddPooledDbContextFactory<TContext>(
1103+
serviceCollection,
1104+
static (_, __) => { },
1105+
poolSize);
1106+
1107+
10311108
/// <summary>
10321109
/// Configures the given context type in the <see cref="IServiceCollection" />.
10331110
/// </summary>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.EntityFrameworkCore.Internal;
5+
6+
namespace Microsoft.EntityFrameworkCore.Infrastructure;
7+
8+
public class AddPooledDbContextFactoryParameterlessTest
9+
{
10+
private sealed class TestDbContext : DbContext
11+
{
12+
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options) { }
13+
}
14+
15+
[Fact]
16+
public async Task Parameterless_factory_should_use_ConfigureDbContext_options()
17+
{
18+
var services = new ServiceCollection();
19+
20+
services.ConfigureDbContext<TestDbContext>((sp, opts) =>
21+
opts.UseInMemoryDatabase("test_db"));
22+
23+
services.AddPooledDbContextFactory<TestDbContext>();
24+
25+
using var provider = services.BuildServiceProvider();
26+
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
27+
await using var db = await factory.CreateDbContextAsync();
28+
29+
Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", db.Database.ProviderName);
30+
}
31+
32+
[Fact]
33+
public async Task Parameterless_factory_with_custom_pool_size_should_still_resolve()
34+
{
35+
var services = new ServiceCollection();
36+
37+
services.ConfigureDbContext<TestDbContext>((sp, opts) =>
38+
opts.UseInMemoryDatabase("test_db_custom_pool"));
39+
40+
services.AddPooledDbContextFactory<TestDbContext>(poolSize: 256);
41+
42+
using var provider = services.BuildServiceProvider();
43+
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
44+
await using var db = await factory.CreateDbContextAsync();
45+
46+
Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", db.Database.ProviderName);
47+
}
48+
49+
[Fact]
50+
public void Scoped_resolution_of_TContext_uses_pooled_factory()
51+
{
52+
var services = new ServiceCollection();
53+
54+
services.ConfigureDbContext<TestDbContext>((sp, opts) =>
55+
opts.UseInMemoryDatabase("scoped_db"));
56+
57+
services.AddPooledDbContextFactory<TestDbContext>();
58+
59+
using var provider = services.BuildServiceProvider();
60+
61+
using var scope = provider.CreateScope();
62+
var ctx = scope.ServiceProvider.GetRequiredService<TestDbContext>();
63+
Assert.Equal("Microsoft.EntityFrameworkCore.InMemory", ctx.Database.ProviderName);
64+
}
65+
66+
[Fact]
67+
public void Pooled_services_are_registered_and_singleton()
68+
{
69+
var services = new ServiceCollection();
70+
71+
services.ConfigureDbContext<TestDbContext>((sp, opts) =>
72+
opts.UseInMemoryDatabase("pool_reg_db"));
73+
74+
services.AddPooledDbContextFactory<TestDbContext>();
75+
76+
using var provider = services.BuildServiceProvider();
77+
78+
var pool1 = provider.GetRequiredService<IDbContextPool<TestDbContext>>();
79+
var pool2 = provider.GetRequiredService<IDbContextPool<TestDbContext>>();
80+
81+
// Should be the same singleton instance
82+
Assert.Same(pool1, pool2);
83+
84+
// And the factory should resolve
85+
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
86+
Assert.NotNull(factory);
87+
}
88+
89+
[Fact]
90+
public async Task Parameterless_factory_without_configuration_throws_meaningful_error()
91+
{
92+
var services = new ServiceCollection();
93+
94+
// No ConfigureDbContext here on purpose.
95+
services.AddPooledDbContextFactory<TestDbContext>();
96+
97+
using var provider = services.BuildServiceProvider();
98+
var factory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
99+
await using var db = await factory.CreateDbContextAsync();
100+
101+
// Trigger provider requirement (any DB operation works; EnsureCreated is simple & provider-agnostic)
102+
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
103+
{
104+
await db.Database.EnsureCreatedAsync();
105+
});
106+
}
107+
}

0 commit comments

Comments
 (0)