实体框架核心服务默认生命周期

Entity Framework Core service default lifetime

在ASP.NET Core应用程序中,我可以像这样通过DI注册DbContext

1
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跟踪越多,性能越低。这就是为什么你经常看到只在using(var context = new AppDbContext())块中使用的DbContext

但是,在Web应用程序中,使用using块是不好的,因为生命周期由框架管理,如果您将其初始处理,则之后的调用将因异常而失败。

如果您在另一侧使用瞬态生命周期,则将失去"事务"功能。使用作用域时,DbContext的事务范围与请求一样长。

如果您需要更细粒度的控制,则必须使用工作单元模式(DbContext已经可以使用)。

对于你的第二个问题:

如果您创建服务,则其生命周期必须等于范围或更短的范围(读取:Scoped或瞬态)。

如果您明确需要更长的服务生命周期,则应在服务中注入DbContext工厂服务或工厂方法。

你可以用类似的东西来完成这个

1
2
services.AddTransient<Func<AppDbContext>>( (provider) => new Func<MyDbContext>( () => new AppDbContext()));
services.AddSingleton<IMySingletonService, MySingletonService>();

您的服务可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,它创建了一个可以重用的上下文池。范围仍然相同,但实例将被"重置"并返回池中。我原本以为"重置"的开销与创建新的开销相同,但我想情况并非如此。

If this method is used, at the time a DbContext instance is requested
by a controller we will first check if there is an instance available
in the pool. Once the request processing finalizes, any state on the
instance is reset and the instance is itself returned to the pool.+

This is conceptually similar to how connection pooling operates in
ADO.NET providers and has the advantage of saving some of the cost of
initialization of DbContext instance.

https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#high-performance


不可否认,这就是我所学到的。它来自懒惰的KISS虔诚。我避免了其他的复杂问题,并解决了我的早期EF Core DbContext配置问题,没有关注池,范围等。我只是把一个POC放在一起,不关心异步返回值,从Controller链接到存储库等等所有基本上都返回"无效",即字面上的单一"任务"。这导致我的DbContext成员在较低的例程中进行迭代,以便莫名其妙地处理底层的DbContext。我所要做的就是让每个异步方法返回一个Task值,一切正常。 EF Core不喜欢async void返回值。