Adding interceptor in dbcontext gives Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning exception

huangapple go评论69阅读模式
英文:

Adding interceptor in dbcontext gives Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning exception

问题

在 EF Core 7 中,我有以下使用 .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;
    	}
    }

在应用程序的一些使用后,它会抛出一个 ```ManyServiceProvidersCreatedWarning``` 异常:

    发生了一个关于警告 'Microsoft.EntityFrameworkCore.Infrastructure.ManyServiceProvidersCreatedWarning' 的错误:Entity Framework 已为内部使用创建了超过二十个 'IServiceProvider' 实例。这通常是由于将新的单例服务实例注入到每个 DbContext 实例中所导致的。

当我注释掉 ```optionsBuilder.AddInterceptors(new IgnoreTrackingInterceptor());``` 时,应用程序可以正常运行数小时。我在这里做错了什么?
英文:

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?

答案1

得分: 5

代码部分不需要翻译,以下是翻译好的内容:

问题在于 IMaterializationInterceptor 是 EF Core 中的 ISingletonInterceptor 接口之一

所有注册为 Singleton 服务的 Entity Framework 拦截器的基本接口。这意味着许多 DbContext 实例都使用单个实例。实现必须是线程安全的。

尽管该接口不添加成员,但 EF Core 基础设施将其用作标记,实际上期望实现要么在 DI 中注册为 Singleton,要么是一个静态实例,因为它们是 EF Core 服务提供程序的缓存键哈希代码和相等性的一部分(它们是按引用比较的)。

因此,将代码修改如下:

public class IgnoreTrackingInterceptor : IMaterializationInterceptor
{
    public static IgnoreTrackingInterceptor Instance { get; } = new();
    private IgnoreTrackingInterceptor() { }
    // 其余保持不变
}

以及

optionsBuilder.AddInterceptors(IgnoreTrackingInterceptor.Instance);

问题应该得到解决。

英文:

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 DbContext instances. 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.

答案2

得分: 2

Ivan的答案 很好地解决了这个问题。

为了进一步澄清发生了什么,我认为EF查看选项(DbContext的配置)以了解您正在使用多少个_功能上不同_的DbContext。它似乎认为超过二十个是一个错误的迹象。

但是,EF通过其内容的相等性来比较选项。拦截器是简单的引用类型,因此默认情况下具有引用相等性。因此,如果每个选项对象都获得一个新的拦截器实例,突然之间选项对象被认为是_不同的_,从而导致每个选项都计入EF的二十个限制。

集成测试是可能出现此问题的常见示例,每个集成测试都使用自己的DI容器并注册具有新拦截器实例的相同DbContext。而不是注册20多个在功能上相同的DbContext,EF _认为_它看到的是20多个_功能上不同_的DbContext的注册。

如果Ivan的答案在您的代码库中是一个麻烦,另一个解决方案是将拦截器的不同实例视为相同。

internal class MyInterceptor : IMaterializationInterceptor
{
	// 使无状态实例像单例一样操作,以避免混淆EF (https://stackoverflow.com/a/76200685/543814)
	public override int GetHashCode() => 1;
	public override bool Equals(object? obj) => obj is MyInterceptor;

	// 剪辑
}

请注意,只有在您的拦截器真正是无状态的情况下才有意义,这通常也是一个良好的实践。

英文:

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.

答案3

得分: 1

不要有别的内容。以下是要翻译的内容:

对于任何遇到相同问题的人,我注意到在DbContextOnConfiguring内设置这个会在进行内存分析后创建许多拦截器实例。

我通过在DI初始化期间添加拦截器来解决这个问题:

DbContextOptionsBuilder<MyDbContext> dbContextOptionsBuilder = new DbContextOptionsBuilder<MyDbContext>()
    .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
    .AddInterceptors(new IgnoreTrackingInterceptor());

不知道Microsoft是否在任何示例中提供了这个(我找不到任何示例),至少他们没有提供任何基本示例,真是可惜。希望这对某人有所帮助。

英文:

For anyone out there that has the same issue, I noticed that setting this inside OnConfiguring of DbContext created many instances of the Interceptor after doing a memory profiling.

I resolved this by adding the intereceptor during DI initialization

DbContextOptionsBuilder<MyDbContext> dbContextOptionsBuilder = new DbContextOptionsBuilder<MyDbContext>()
	.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
	.AddInterceptors(new IgnoreTrackingInterceptor());

Don't know if Microsoft provides this in any of their examples (I couldn't find any) and it is really shame that at least they don't provide any basic examples. Hopefully this helps someone.

huangapple
  • 本文由 发表于 2023年3月1日 16:45:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75601346.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定