Entity Framework Core 服务默认生命周期
Posted
技术标签:
【中文标题】Entity Framework Core 服务默认生命周期【英文标题】:Entity Framework Core service default lifetime 【发布时间】:2016-09-27 05:20:32 【问题描述】:在 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?
【问题讨论】:
【参考方案1】:是的,DbContext
的默认生命周期是有范围的。这是有意的。
实例化DbContext
非常便宜,它确保您不会使用很多资源。如果您的DbContext
具有单例生存期,那么您读取过的所有记录都将由DbContext
跟踪,除非您特别禁用跟踪。这将需要更多的内存使用,并且会不断增长。
而且DbContext
的轨道越多,性能就越低。这就是为什么你经常看到DbContext
只在using(var context = new AppDbContext())
块中使用。
然而,在 Web 应用程序中,使用 using
块是不好的,因为生命周期是由 framework 管理的,如果您将其提前处理,那么之后的调用将失败并出现异常。
如果您在另一边使用瞬态生命周期,您将失去“事务”功能。使用作用域,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();
【讨论】:
在网络应用程序中,它真的是管理生命周期的控制器吗?还是在请求结束时处理每个请求范围内的事物的 DI?我想更好地理解这一点 我的错。我的意思是说框架。在它的CreateController
method 中有一个控制器工厂(IControllerFactory
,默认实现见github.com/aspnet/Mvc/blob/dev/src/…),它创建控制器并处理它并处理它,它是ReleaseController()
方法中的依赖项。另请参阅此 GitHub 问题,了解有关内部工作原理的更多详细信息以及它为何如此工作:github.com/aspnet/Mvc/issues/3727
谢谢。如果我有一个在其构造函数中采用 DbContext 并实现 IDisposable 的存储库,是否应该从其自己的 dispose 方法调用 DbContext 上的 dispose?我注意到 EF UserStore for Identity 没有在 dbcontext 上调用 dispose 但我觉得不这样做是不对的,所以我试图理解他们为什么不这样做
如果你直接注入DbContext
那么不,不要丢弃它。请注意,在我的回答中,我传递了一个委托 Func<AppDbContext> contextFactory
,代表在每次调用它时都会创建一个新的工厂实例
关于选择跟踪的 5 美分。如果您不想跟踪实体的集合,那么您可以使用 AsNoTracking() 方法。我不是 100% 确定,但理论上它应该可以节省资源并减少查询时间。【参考方案2】:
注意:在 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
【讨论】:
猜测可能是“OnModelCreating”函数在处理大型数据库时需要时间?【参考方案3】:诚然,这是轶事,这是我学到的。它来自懒惰的 KISS 奉献。我避免了其他并发症并解决了我的过早的 EF Core DbContext 处理不关心池、范围等的问题。我只是将一个不考虑异步返回值的 POC 放在一起,从控制器链接到存储库等等on,基本上都返回了“void”,即字面上的单数“Task”。这导致我在较低例程中的 DbContext 成员之间进行迭代,以莫名其妙地处理底层 DbContext。我所要做的就是让每个异步方法返回一个Task<whatever return>
值,然后一切正常。 EF Core 不喜欢异步 void 返回值。
【讨论】:
以上是关于Entity Framework Core 服务默认生命周期的主要内容,如果未能解决你的问题,请参考以下文章
无法更新 Entity Framework Core 中的标识列
如何使用 Entity Framework Core 正确保存 DateTime?
UWP开发之ORM实践:如何使用Entity Framework Core做SQLite数据持久层?
Entity Framework Core - 在尝试运行第二个查询时释放