I have the following code in EF Core 7 using .NET 7:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.AddInterceptors(new IgnoreTrackingInterceptor());
}
public class IgnoreTrackingInterceptor : IMaterializationInterceptor
{
public object InitializedInstance(MaterializationInterceptionData materializationData, object instance)
{
if (instance is ISomeEntity someEntity)
materializationData.Context.Entry(someEntity).State = EntityState.Detached;
return instance;
}
}
After some usage of the app it throws a ManyServiceProvidersCreatedWarning exception.
An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning': More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance
When I comment the optionsBuilder.AddInterceptors(new IgnoreTrackingInterceptor()); app works without any issues for hours.
What am I doing wrong here?
The problem is that the IMaterializationInterceptor is one of the EF Core ISingletonInterceptor interfaces which
The base interface for all Entity Framework interceptors that are registered as Singleton services. This means a single instance is used by many
DbContextinstances. The implementation must be thread-safe.
Even though the interface does not add members, EF Core infrastructure uses it as a marker and really expects the implementation to be either registered as Singleton in DI or be a static instance, because these are part of EF Core service provider cache key hash code and equality (they are compared by reference).
So modify the code as follows:
public class IgnoreTrackingInterceptor : IMaterializationInterceptor
{
public static IgnoreTrackingInterceptor Instance { get; } = new();
private IgnoreTrackingInterceptor() { }
// the rest as is
}
and
optionsBuilder.AddInterceptors(IgnoreTrackingInterceptor.Instance);
and the problem should be solved.
Ivan's answer solves the problem well.
To further clarify what happens, I believe that EF looks at the options (the DbContext's configuration) to get a sense of how many functionally different DbContexts you are using. It seems to consider more than twenty to be an indication of a mistake.
However, EF compares options by the equality of their contents. And interceptors, being simple reference types, have reference equality by default. Because of this, if each options object gets a new instance of an interceptor, suddenly the options objects are considered different, causing each to count towards EF's limit of twenty.
Integration tests are a common example where this issue may occur, when each integration test uses its own DI container and registers the same DbContext with a fresh interceptor instance. Instead of 20+ registrations of functionally the same DbContext, EF believes it is seeing 20+ registrations of functionally different DbContexts.
If Ivan's answer of using a truly singleton instance is an annoyance in your code base, an alternative solution is to have different instances of your interceptor be considered the same.
internal class MyInterceptor : IMaterializationInterceptor
{
// Have stateless instances act like a singleton, to avoid confusing EF (https://stackoverflow.com/a/76200685/543814)
public override int GetHashCode() => 1;
public override bool Equals(object? obj) => obj is MyInterceptor;
// Snip
}
Note that this only makes sense if your interceptor is truly stateless, which is generally good practice anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With