根提供者注入到由新作用域提供者解析的服务中

后端开发 2026-07-10

我有一个Blazor服务器应用程序。我有一个 IHostedService,它打算在 StartAsync() 方法中执行启动任务:

builder.Services.AddHostedService<StartupTask>();

它的构造函数注入了 IServiceScopeFactory。它创建了一个新作用域:

var provider = serviceScopeFactory
    .CreateScope()
    .ServiceProvider;
provider
    .GetRequiredService<EntityService>()
    .ReturnMyCtorInjectedProvider()
    .GetRequiredService<EntityService>();

其中 EntityService 是一个 DbContext,它接收一个在构造函数中注入的 IServiceProvider

据我所知,由于 EntityService 已经衍生自某个作用域的 IServiceProvider,它本应接收一个同样绑定在该作用域上的 IServiceProvider,从中解析同一服务的行为应该与从新创建的作用域的 IServiceProvider 解析该服务时的行为相同,但我却遇到了一个众所周知的错误:

作用域服务无法从根提供程序解析

所以看起来构造函数似乎被注入了根提供程序,但这背后的原因是什么?我到底漏掉了什么?

完整代码 - 这是 EntityService 的构造函数:

public class EntityService (DbContextOptions<EntityService> options,
        IServiceProvider serviceProvider,
        IEncryptionService encryptionService) : DbContext(options)
{
    // ...
}

这是我的 IHostedService 的构造函数:

public class StartupCleaner(IServiceScopeFactory serviceScopeFactory) : IHostedService
{
    // ...
}

它们的注册如下:

builder.Services.AddDbContextPool<EntityService>(
    opts => opts.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);
builder.Services.AddHostedService<StartupCleaner>();

这是在 StartupCleaner 启动时调用的方法:

public Task StartAsync(CancellationToken cancellationToken)
{
    var sp = serviceScopeFactory
                 .CreateScope()
                 .ServiceProvider;

    var entityService = sp.GetRequiredService<EntityService>();
    entityService.ReturnMyCtorInjectedProvider()
                 .GetRequiredService<EntityService>();
    // ...
}

这是 EntityService 中的方法:

public IServiceProvider ReturnMyCtorInjectedProvider()
{
    return serviceProvider;
}

我得到的错误:

无法从根提供程序解析作用域服务 'Axoneckend.Services.EntityService'

解决方案

这并不是作用域以及依赖注入总体工作原理的问题,而是你选择使用 AddDbContextPool 的问题:

备注

DbContext池化可以在高吞吐量场景中通过重用上下文实例来提升性能。然而,对于大多数应用而言,这种性能提升很小。请注意,在使用池化时,上下文的配置在使用之间不能改变,并且 注入到上下文中的作用域服务将只会从初始作用域解析一次。只有在性能测试表明它确实带来提升时,才考虑使用DbContext池化。

所以,我认为这里的初始作用域就是根作用域,它被用来创建那些池化实例。你手动创建的作用域并不是传递给构造函数的那个作用域。

下面给出一个最小示例,确认普通(非池化)作用域解析按你预期地工作:

public static void Main() {
    var serviceCollection = new ServiceCollection();

    serviceCollection.AddScoped<DbContext>();
    serviceCollection.AddSingleton<HostedServiceSingleton>();

    var serviceProvider = serviceCollection.BuildServiceProvider();

    _ = serviceProvider.GetService<HostedServiceSingleton>();

}

public class DbContext {
    public IServiceProvider ReturnMyCtorInjectedProvider { get; }

    public DbContext(IServiceProvider provider) {
        Console.WriteLine(provider.GetHashCode());
        ReturnMyCtorInjectedProvider = provider;
    }
}

public class HostedServiceSingleton {
    public HostedServiceSingleton(IServiceScopeFactory scopeFactory) {
        var provider = scopeFactory.CreateScope().ServiceProvider;
        Console.WriteLine(provider.GetHashCode());

        // no surprises
        var injectedProvider = provider.GetService<DbContext>()
                    .ReturnMyCtorInjectedProvider;

        // same hash code
        Console.WriteLine(injectedProvider.GetHashCode());

        var anotherDbContextResolution = injectedProvider
                    .GetService<DbContext>();
    }
}

示例输出:

30631159
30631159
30631159
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章