ChatGPT解决这个技术问题 Extra ChatGPT

Entity Framework Core 服务默认生命周期

在 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?


A
Anthony Johnston

是的,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();
    }
}

在网络应用程序中,它真的是管理生命周期的控制器吗?还是在请求结束时处理每个请求范围内的事物的 DI?我想更好地理解
我的错。我的意思是说框架。在它的 CreateController 方法中有一个控制器工厂(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% 确定,但理论上它应该可以节省资源并减少查询时间。
E
Edward Brey

注意:在 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”函数需要时间处理大型数据库?
K
KramFfud

无可否认,这是我所学到的。它来自一个懒惰的 KISS 奉献。我避免了其他并发症并解决了我的过早的 EF Core DbContext 处理不关心池、范围等的问题。我只是将一个不考虑异步返回值的 POC 放在一起,从控制器链接到存储库等等on,它们基本上都返回了“void”,即字面上的单数“Task”。这导致我在较低例程中的 DbContext 成员之间进行迭代,以莫名其妙地处理底层 DbContext。我所要做的就是让每个异步方法返回一个 Task<whatever return> 值,然后一切正常。 EF Core 不喜欢异步 void 返回值。