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

问题描述:

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

In ASP.NET Core application I can register DbContext through DI like this

services.AddDbContext<Models.ShellDbContext>(options => options.UseNpgsql(connection));

知道它的生命周期有多长很有趣?

And it is intersting to know what is its lifetime?

从这里

From here https://github.com/aspnet/EntityFramework/blob/f33b76c0a070d08a191d67c09650f52c26e34052/src/Microsoft.EntityFrameworkCore/EntityFrameworkServiceCollectionExtensions.cs#L140 it looks like it is configured as Scoped that means DbContext instance is created on every request.

所以问题的第一部分是:是真的吗?如果是,那么它的成本是多少?

So first part of the question is: Is it true and if yes, then how costly it is?

第二部分是:如果我创建了一个使用 DbContext 的服务,并打算由控制器使用,并且将有一个 API 来管理数据库中的某些实体,它是否也应该注册为 Scoped?

And second part is: If I create a service what consumes DbContext, and intended to be consumed by Controllers, and will have an API to manage some entities in DB, should it be registered as Scoped also?

是的,DbContext 的默认生命周期是有范围的.这是这样的.

Yes, the default life time for DbContext is scoped. This is intended this way.

实例化 DbContext 非常便宜,它确保您不使用许多资源.如果您有一个具有单例生命周期的 DbContext,那么您读取一次的所有记录都将被 DbContext 跟踪,除非您明确禁用跟踪.这将需要更多的内存使用量,并且会继续增长.

Instantiating DbContext is pretty cheap and it makes sure that the your do not use to many resources. If you'd have a DbContext with a singleton lifetime, then all records that you read once will be tracked by the DbContext, unless you specifically disable tracking. This will require much more memory usage and it will keep growing.

DbContext 跟踪的越多,性能就越低.这就是为什么您经常看到 DbContext 仅在 using(var context = new AppDbContext()) 块中使用的原因.

And the more the DbContext tracks, the lower the performance will be. That's why you often see DbContext only used within a using(var context = new AppDbContext()) block.

然而,在 web 应用程序中,使用 using 块是不好的,因为生命周期由 framework 并且如果您提前处置它,之后的调用将失败并出现异常.

In web applications however, using the using block is bad, because the lifetime is managed by the framework and if you dispose it to early the calls after that will fail with an exception.

如果你在另一边使用瞬态生命周期,你将失去交易"功能.有了作用域,DbContext 有一个与请求一样长的事务作用域.

If you use transient lifetime on the other side, you will lose the "transaction" functionality. With scoped, the DbContext has a transaction scope that's as long as the request.

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

If you need more fine-grained control, you have to use the Unit of Work pattern (which DbContext already kind of utilize).

对于你的第二个问题:

如果您创建一个服务,它的生命周期必须等于范围之一或更短(阅读:范围或瞬态).

If you create a service, it must have a lifetime that's equal to the one of the scope or shorter (read: Scoped or transient).

如果您明确需要更长的服务生命周期,您应该将 DbContext 工厂服务或工厂方法注入您的服务.

If you explicitly need a longer life-time for a service, you should inject a DbContext factory service or factory method into your service.

你可以用像

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

您的服务可能如下所示:

And your service may look like this:

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