Entity Framework 核心服务默认生命周期
Entity Framework Core service default lifetime
在ASP.NET核心应用中我可以像这样通过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 来管理数据库中的一些实体,它是否也应该注册为 Scoped?
是的,DbContext
的默认生命周期是有限的。这是打算这样的。
实例化 DbContext
非常便宜,它确保您不会使用太多资源。如果您的 DbContext
具有单例生命周期,那么您读取一次的所有记录都将被 DbContext
跟踪,除非您特别禁用跟踪。这将需要更多的内存使用量,并且会不断增长。
而且DbContext
曲目越多,性能越低。这就是为什么您经常看到 DbContext
仅在 using(var context = new AppDbContext())
块中使用。
然而,在 Web 应用程序中,使用 using
块是不好的,因为生命周期由 framework 管理,如果您将其提前放置,之后的调用将失败并出现异常。
如果在另一端使用瞬态生命周期,您将失去 "transaction" 功能。使用范围,DbContext
具有与请求一样长的事务范围。
如果您需要更细粒度的控制,则必须使用工作单元模式(DbContext
已经在使用)。
第二个问题:
如果您创建服务,它的生命周期必须等于范围之一或更短(阅读:Scoped 或 transient)。
如果您明确需要更长的服务生命周期,您应该将 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();
}
}
注意:在 EF Core 2 中,现在有一种新方法 AddDbContextPool
可以创建可重复使用的上下文池。范围仍然相同,但实例将 'reset' 并返回到池中。我原以为 'resetting' 的开销与创建一个新开销一样,但我想情况并非如此。
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 处理 w/o 池、范围等问题的问题。我只是将 POC w/o 关于异步 return 值的问题放在一起,链接起来从 Controller 到 Repository 等等,本质上都是 returned “void”,即字面上的单数“Task”。这导致我的 DbContext 成员在较低的例程中进行迭代,以莫名其妙地处理底层 DbContext。我所要做的就是使每个异步方法 return 成为 Task<whatever return>
值,一切正常。 EF Core 不喜欢 async void return 值。
在ASP.NET核心应用中我可以像这样通过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 来管理数据库中的一些实体,它是否也应该注册为 Scoped?
是的,DbContext
的默认生命周期是有限的。这是打算这样的。
实例化 DbContext
非常便宜,它确保您不会使用太多资源。如果您的 DbContext
具有单例生命周期,那么您读取一次的所有记录都将被 DbContext
跟踪,除非您特别禁用跟踪。这将需要更多的内存使用量,并且会不断增长。
而且DbContext
曲目越多,性能越低。这就是为什么您经常看到 DbContext
仅在 using(var context = new AppDbContext())
块中使用。
然而,在 Web 应用程序中,使用 using
块是不好的,因为生命周期由 framework 管理,如果您将其提前放置,之后的调用将失败并出现异常。
如果在另一端使用瞬态生命周期,您将失去 "transaction" 功能。使用范围,DbContext
具有与请求一样长的事务范围。
如果您需要更细粒度的控制,则必须使用工作单元模式(DbContext
已经在使用)。
第二个问题:
如果您创建服务,它的生命周期必须等于范围之一或更短(阅读:Scoped 或 transient)。
如果您明确需要更长的服务生命周期,您应该将 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();
}
}
注意:在 EF Core 2 中,现在有一种新方法 AddDbContextPool
可以创建可重复使用的上下文池。范围仍然相同,但实例将 'reset' 并返回到池中。我原以为 'resetting' 的开销与创建一个新开销一样,但我想情况并非如此。
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 处理 w/o 池、范围等问题的问题。我只是将 POC w/o 关于异步 return 值的问题放在一起,链接起来从 Controller 到 Repository 等等,本质上都是 returned “void”,即字面上的单数“Task”。这导致我的 DbContext 成员在较低的例程中进行迭代,以莫名其妙地处理底层 DbContext。我所要做的就是使每个异步方法 return 成为 Task<whatever return>
值,一切正常。 EF Core 不喜欢 async void return 值。