我有一个使用DB上下文的托管服务,以及一个也使用DB上下文的范围服务。看起来或多或少是这样的
public class HostedWorker : BackgroundService
{
private readonly IServiceScopeFactory scopeFactory;
public HostedWorker(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = scopeFactory.CreateScope())
{
var database = scope.ServiceProvider
.GetRequiredService<DatabaseContext>();
var entity = database.GetSomeEntity();
scope.ServiceProvider
.GetRequiredService<ISomeScopedService>()
.DoSomethingWithTheEntity(entity)
}
}
}
public class SomeScopedService: ISomeScopedService
{
private readonly DatabaseContext context;
public SomeScopedService(DatabaseContext context)
{
this.context = context;
}
public void DoSomethingWithTheEntity(SomeEntity entity)
{
// the context doesn't track the entity
}
}
作用域服务从依赖项注入获取数据库上下文,事实证明,它与我直接从ServiceProvider获得的上下文不同。结果是,范围服务的数据库上下文无法跟踪我从托管服务的上下文中获取的实体。
在所有其他情况下,上下文是相同的:通过DI获取数据库的多个范围服务都共享相同的上下文,并且当我在单例服务之间传递手动创建的范围时,它们都得到相同的GetRequiredService
中的上下文。只有当我尝试混合时,我才能得到这个结果。
有没有办法在整个范围内获得相同的数据库上下文?
UPD 错误最终与数据库上下文无关(对于范围内的不同服务而言确实如此),但是我将在此保留问题,因为答案可能对其他一些情况。
答案 0 :(得分:3)
与我直接从ServiceProvider获得的上下文不同
首先,永远不要直接从服务提供商那里检索数据库上下文。您始终应该在服务范围内获得它,否则最终将获得数据库上下文和在应用程序生命周期内不会被丢弃的连接。
因此,您应该始终在服务范围内使用数据库上下文。这是在请求管道中隐式完成的(例如在控制器中),但在托管服务中,您将必须自己创建服务范围。您还应该确保不要长时间保持该示波器处于打开状态;最好设计您的服务,使其只在非常有限的时间内访问数据库,并具有多个短期服务范围,而不是一个长期范围。
using (var scope = scopeFactory.CreateScope())
{
var database = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var entity = database.GetSomeEntity();
scope.ServiceProvider.GetRequiredService<ISomeScopedService>()
.DoSomethingWithTheEntity(entity)
}
假设ISomeScopedService
已正确注册为作用域依赖项,则此代码应解析相同的数据库上下文实例。数据库上下文被注册为作用域的依赖项(默认情况下),因此这应该从作用域服务提供者和作用域服务中的构造函数注入中解析相同的实例。
如果这对您不起作用,则您的实际代码可能看起来有所不同,使用了不同的范围(或没有范围),或者GetSomeEntity()
背后的逻辑检索了一个非跟踪实体。