dotnet / dotnet/efcore

Is `ManyServiceProvidersCreatedWarning` expected when adding services in OnConfiguring and "externally"

Open
#30,191 12 comments 2 reactions 0 assignees View on GitHub
area-dbcontext customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

This is not a bug report but rather a request for guidance

## Ask a question
Will adding extensions/configuration inside of `OnConfiguring` lead to Service Provider caching issues if you **also** add extensions via other other means (DI registration, test setup) external to the context?

I am trying to figure out what is causing the `ManyServiceProvidersCreatedWarning` error to be emitted during our tests runs. I've read the various issues throughout the repository and I understand that it's usually indicative of SP caching not functioning due to some singleton service, etc. I've been trying to trace down the root cause and I've found a work-around that has me worried that we are configuring our contexts/applications incorrectly. I'm looking for some guidance.

I don't have a minimal repro yet as there's a lot of moving parts. In the meantime I hope that something in my description below jumps out.

### Include your code

We have a base `DbContext` class that we share throughout all of our projects. As part of `OnConfiguring` it adds a couple of extensions--naming conventions and an internal extension (see below)--guarded behind `Options.IsFrozen`

```csharp
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
if (!optionsBuilder.Options.IsFrozen)
{
optionsBuilder.UseSnakeCaseNamingConvention();
optionsBuilder.UseOurExtensionAndConfiguration();
}
}
```

In a _normal_ application we register our contexts in DI via `AddDbContext`:

```csharp
NpgsqlDataSource dbDataSource = GetDataSourceSomehow();
services.AddSomeInterceptors();
services.AddDbContext((s, o) =>
{
o.UseNpgsql(dbDataSource);
o.AddInterceptors(s.GetServices());
});
```

We expect all of our applications to use the naming conventions and our custom extension. The `UseOurExtensionAndConfiguration` adds an `IDbContextOptionsExtension` which in turn registers some interceptors and a `ConventionSetPlugin` and calls `ConfigureWarnings` to set the log level for a few relational events. We don't want to repeat it in every application which is why we've put it in our base context's `OnConfiguring` as highlighted above. So far this _seemed_ to be working fine.

Recently, however we began to experience the `ManyServiceProvidersCreatedWarning` in our tests. We use an xUnit class fixture that initializes Postgres containers (via [Testcontainers](https://github.com/testcontainers/testcontainers-dotnet)), configures test-specific `DbContextOptions` and provides a method for our test classes to create a new context. Each class gets it's own instance of the fixture (can't be shared due to some unrelated functionality I've elided) that looks like the following:

```csharp
public abstract class TestContextFixture where T : OurContextBaseClass
{
private readonly Lazy> _optionsLazy;
protected TestContextFixture() =>
_optionsLazy = new Lazy>(CreateOptions);

// filled in by Testcontainers
protected string ConnectionString { get; set; } = null!;
// overridden in sub-class which passes options to context constructor
protected abstract T CreateContext(DbContextOptions options);
// called by Unit Tests
public T CreateContext() => CreateContext(_optionsLazy.Value);

private DbContextOptions CreateOptions()
{
var options = new DbContextOptionsBuilder();
options.UseNpgsql(ConnectionString)
.EnableDetailedErrors()
.EnableSensitiveDataLogging()
.ConfigureWarnings(static w =>
w.Ignore(
CoreEventId.SensitiveDataLoggingEnabledWarning
)
);
return options.Options;
}
}
```

#### The workaround?
We've started to notice that as we add more test classes we are hitting the `ManyServiceProvidersCreatedWarning` issue. After a lot of guess-and-check debugging, I discovered that adding the following two lines into the `CreateOptions` method prevents this problem:

```csharp
options.UseSnakeCaseNamingConvention();
options.UseOurExtensionAndConfiguration();
```
You'll note that these are the **same two methods** that we are calling in `OnConfiguring` guarded by the `IsFrozen` check. If either is omitted I get the errors again. It seems that EF builds the options twice--before the constructor and then again for `OnConfiguring`--considers them different, and thus must create a new internal provider.

#### Summary/Question
Is adding extensions/performing configuring guarded behind `IsFrozen` valid, particularly if we are going to provide additional configuration "outside" the context? Is there something downright wrong about what we're doing? What's the recommended pattern for stuff like this--do I need to consolidate all extension setup into one place?

We're working on a set of greenfield applications that aren't getting much load at the moment. I'm worried that since we don't call these methods in our _normal_ DI registration flow that we are going to eventually hit the same problem in our application as we do in our tests. If this is just likely due to the way our tests are set up I'd have no issue silencing the warning there,but I'd like to take preventative measures if this is likely a larger issue.

### Include provider and version information

EF Core version: 7.0.2
Database provider: efcore.pg
Target framework: NET7.0
Operating system: Windows11, debian (containers)
IDE: VS 2022

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.