测试期间的 EF Core 内部缓存和许多 DbContext 类型

EF Core internal caching and many DbContext types during testing

我有很多测试类,每个测试都有几十个。我想隔离测试,所以我使用 MyDbContextToTestFooMyDbContextToTestBarMyDbContextToTestBaz 等而不是大型上下文 MyDbContext。所以我有很多 DbContext 子类.

在我使用 EF Core 5 进行的单元测试中,我 运行 进入了 ManyServiceProvidersCreatedWarning。他们单独工作,但当 运行 作为一个团队时,很多人都失败了:

System.InvalidOperationException : 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. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. This exception can be suppressed or logged by passing event ID 'CoreEventId.ManyServiceProvidersCreatedWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.

我没有像那个错误提示的那样对 DbContextOptionsBuilder 做任何奇怪的事情。我不知道如何诊断“......可能需要建立新的服务提供商”。在大多数测试中,我通常会创建一个上下文:new DbContextOptionsBuilder<TContext>().UseSqlite("DataSource=:memory:") 其中 TContext 是我上面提到的上下文类型之一。

我已经阅读了关于 repo 的许多问题,并发现 EF 对各种事物进行了大量缓存,但不存在关于该主题的文档。推荐的是“找出这么多服务提供者被缓存的原因”,但我不知道该找什么

有两个workarounds:

我假设“服务提供者”是指 EF 的内部 IoC 容器。

我想知道的是:我有很多 DbContext 类型(因此 IModel 类型)这一事实会影响服务提供商缓存吗?两者有关系吗? (我知道 EF 为每个 DbContext 缓存一个 IModel,它是否也为每个缓存一个服务提供者?)

服务提供商缓存完全基于上下文选项配置 - 上下文类型、模型等无关紧要。

在 EF Core 5.0 中,根据 source code 的键是

static long GetCacheKey(IDbContextOptions options) => options.Extensions
    .OrderBy(e => e.GetType().Name)
    .Aggregate(0L, (t, e) => (t * 397) ^ ((long)e.GetType().GetHashCode() * 397) ^ e.Info.GetServiceProviderHashCode());

而在 EF Core 6.0 中,关键是具有类似语义的重写 Equals 方法的选项实例。

所以您使用的选项有所不同 - 如果您要覆盖它并修改其中的选项,则最初或在 OnConfiguring 调用之后。这是你需要弄清楚的(在 5.0 中你可以使用上面的方法来检查密钥,在 6.0 中你可以使用一些静态字段来存储第一个选项实例并用它来检查 Equals 下一个).

请注意,EF Core 会缓存原始调用选项和 OnConfiguring 调用选项后的选项,因此它们都算在内。顺便说一下,生成警告的代码在同一个地方 (class) - source.