在 ASP.NET Core 应用程序中,我可以像这样通过 DI 注册 DbContext
services.AddDbContext<Models.ShellDbContext>(options => options.UseNpgsql(connection));
知道它的寿命是多少很有趣?
从这里 https://github.com/aspnet/EntityFramework/blob/f33b76c0a070d08a191d67c09650f52c26e34052/src/Microsoft.EntityFrameworkCore/EntityFrameworkServiceCollectionExtensions.cs#L140 看起来它被配置为 Scoped,这意味着 DbContext 实例是在每个请求上创建的。
所以问题的第一部分是:这是真的吗?如果是,那么它的成本是多少?
第二部分是:如果我创建一个使用 DbContext 的服务,并打算由控制器使用,并且将有一个 API 来管理 DB 中的某些实体,它是否也应该注册为 Scoped?
是的,DbContext
的默认生命周期是有范围的。这是有意的。
实例化 DbContext
非常便宜,它确保您不会使用很多资源。如果您有一个具有单例生命周期的 DbContext
,那么您读取过一次的所有记录都将由 DbContext
跟踪,除非您特别禁用跟踪。这将需要更多的内存使用,并且会不断增长。
DbContext
轨道越多,性能就越低。这就是为什么您经常看到 DbContext
仅在 using(var context = new AppDbContext())
块中使用。
然而,在 Web 应用程序中,使用 using
块是不好的,因为生命周期是由 framework 管理的,如果您将其提前释放,那么之后的调用将失败并出现异常。
如果您在另一边使用瞬态生命周期,您将失去“事务”功能。对于 scoped,DbContext
的事务范围与请求一样长。
如果您需要更细粒度的控制,则必须使用工作单元模式(DbContext
已经在使用)。
对于你的第二个问题:
如果您创建一个服务,它的生命周期必须等于或短的一个范围(阅读:范围或瞬态)。
如果您明确需要更长的服务生命周期,则应将 DbContext
工厂服务或工厂方法注入您的服务。
你可以用类似的东西来完成这个
services.AddTransient<Func<AppDbContext>>( (provider) => new Func<MyDbContext>( () => new AppDbContext()));
services.AddSingleton<IMySingletonService, MySingletonService>();
您的服务可能如下所示:
public class MySingletonService : IMySingletonService, IDisposable
{
private readonly AppDbContext context;
public MySingletonService(Func<AppDbContext> contextFactory)
{
if(contextFactory == null)
throw new ArgumentNullException(nameof(contextFactory));
// it creates an transient factory, make sure to dispose it in `Dispose()` method.
// Since it's member of the MySingletonService, it's lifetime
// is effectively bound to it.
context = contextFactory();
}
}
注意:在 EF Core 2 中,现在有一个新方法 AddDbContextPool
,它创建了一个可以重用的上下文池。范围仍然相同,但实例将被“重置”并返回到池中。我原以为“重置”的开销与创建一个新的开销相同,但我想情况并非如此。
如果使用此方法,则在控制器请求 DbContext 实例时,我们将首先检查池中是否有可用的实例。一旦请求处理完成,实例上的任何状态都会被重置,并且实例本身会返回到池中。+ 这在概念上类似于 ADO.NET 提供程序中的连接池操作方式,并且具有节省一些初始化成本的优势DbContext 实例。
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#high-performance
无可否认,这是我所学到的。它来自一个懒惰的 KISS 奉献。我避免了其他并发症并解决了我的过早的 EF Core DbContext 处理不关心池、范围等的问题。我只是将一个不考虑异步返回值的 POC 放在一起,从控制器链接到存储库等等on,它们基本上都返回了“void”,即字面上的单数“Task”。这导致我在较低例程中的 DbContext 成员之间进行迭代,以莫名其妙地处理底层 DbContext。我所要做的就是让每个异步方法返回一个 Task<whatever return>
值,然后一切正常。 EF Core 不喜欢异步 void 返回值。
不定期副业成功案例分享
CreateController
方法中有一个控制器工厂(IControllerFactory
,默认实现参见 github.com/aspnet/Mvc/blob/dev/src/…),它创建控制器并处置它,处置它以及它在ReleaseController()
方法中的依赖项。另请参阅此 GitHub 问题,了解有关内部工作原理的更多详细信息以及它为何如此工作:github.com/aspnet/Mvc/issues/3727DbContext
则不,不要丢弃它。请注意,在我的回答中,我传递了一个委托Func<AppDbContext> contextFactory
,委托在每次调用它时都会创建一个新的工厂实例