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?我想更好地理解这一点 我的错。我的意思是说框架。在它的CreateControllermethod 中有一个控制器工厂(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&lt;AppDbContext&gt; 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&lt;whatever return&gt; 值,然后一切正常。 EF Core 不喜欢异步 void 返回值。

【讨论】:

以上是关于Entity Framework Core 服务默认生命周期的主要内容,如果未能解决你的问题,请参考以下文章

无法更新 Entity Framework Core 中的标识列

如何使用 Entity Framework Core 正确保存 DateTime?

UWP开发之ORM实践:如何使用Entity Framework Core做SQLite数据持久层?

Entity Framework Core - 在尝试运行第二个查询时释放

Entity Framework Core 数据库第一种方法复数表名

Entity Framework Core 6.0 预览4 性能改进