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)"/>
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing period at the end of the sentence. The line ends with ServiceLifetime)"/> but should end with ServiceLifetime)"/>. to be consistent with XML documentation style in the rest of the file.

Suggested change
/// <see cref="ConfigureDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}, ServiceLifetime)"/>
/// <see cref="ConfigureDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}, ServiceLifetime)"/>.

Copilot uses AI. Check for mistakes.
/// 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>
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation should include a <para> block about standard pooling behavior (like threading considerations, entity lifetime, etc.) similar to the existing AddPooledDbContextFactory overloads (lines 935-944, 984-995). This would maintain consistency across all factory registration methods and ensure users are aware of important pooling constraints.

Suggested change
/// </para>
/// </para>
/// <para>
/// <b>Pooling behavior:</b> When pooling is enabled, <see cref="DbContext" /> instances are reused to improve performance.
/// Pooled contexts must not be shared between threads or requests, and should be disposed as soon as possible after use.
/// Do not cache or store pooled <see cref="DbContext" /> instances for later use. For more information, see
/// <see href="https://aka.ms/efcore-docs-dbcontext-pooling">Using DbContext pooling</see>.
/// </para>

Copilot uses AI. Check for mistakes.
/// </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"/>.
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing whitespace present at the end of the line. According to the coding guidelines, trailing whitespace should be removed.

Suggested change
/// and allows specifying a custom <paramref name="poolSize"/>.
/// and allows specifying a custom <paramref name="poolSize"/>.

Copilot uses AI. Check for mistakes.
/// Options configured via <c>ConfigureDbContext&lt;TContext&gt;</c> are automatically used,
/// eliminating the need to repeat provider configuration.
/// </para>
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <remarks> section should include additional standard documentation paragraphs similar to existing overloads. Looking at lines 977-1001, the existing overloads document: 1) When to use factory vs direct registration (Blazor scenarios), 2) DI vs. non-DI usage, and 3) Threading constraints. These are important for API consistency and completeness.

Suggested change
/// </para>
/// </para>
/// <para>
/// Use <see cref="AddPooledDbContextFactory{TContext}(IServiceCollection, int)" /> when you need to create
/// <see cref="DbContext" /> instances in scenarios where dependency injection cannot be used, such as in Blazor
/// applications or background services. For typical ASP.NET Core applications where a single context instance is
/// used per request, consider using <see cref="AddDbContext{TContext}(IServiceCollection, Action{DbContextOptionsBuilder}?, ServiceLifetime, ServiceLifetime)" />
/// instead.
/// </para>
/// <para>
/// For applications that don't use dependency injection, consider creating <see cref="DbContext" /> instances
/// directly with its constructor. The <see cref="DbContext.OnConfiguring" /> method can then be overridden to
/// configure a connection string and other options.
/// </para>
/// <para>
/// Entity Framework Core does not support multiple parallel operations being run on the same <see cref="DbContext" />
/// instance. This includes both parallel execution of async queries and any explicit concurrent use from multiple threads.
/// Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute
/// in parallel. See <see href="https://aka.ms/efcore-docs-threading">Avoiding DbContext threading issues</see> for more information
/// and examples.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-di">Using DbContext with dependency injection</see> for more information and examples.
/// </para>

Copilot uses AI. Check for mistakes.
/// </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.
Comment on lines +1091 to +1092
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description "The default is 1024" is misleading. This parameter doesn't have a default value in the method signature (line 1100), so users must explicitly provide it. The documentation should say something like "Sets the maximum number of instances retained by the pool." without mentioning a default, or clarify that 1024 is what the underlying pool uses, not a parameter default.

Suggested change
/// The maximum number of <typeparamref name="TContext" /> instances retained by the pool.
/// The default is 1024.
/// Sets the maximum number of <typeparamref name="TContext" /> instances retained by the pool.

Copilot uses AI. Check for mistakes.
/// </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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int poolSize)
int poolSize = DbContextPool<DbContext>.DefaultPoolSize)

and remove the other overload

where TContext : DbContext
=> AddPooledDbContextFactory<TContext>(
serviceCollection,
static (_, __) => { },
poolSize);


Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line. According to EF Core coding guidelines, there should be no multiple consecutive blank lines. Line 1107 appears to be an extra blank line that should be removed.

Suggested change

Copilot uses AI. Check for mistakes.
/// <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