ChatGPT解决这个技术问题 Extra ChatGPT

我应该如何将 DbContext 实例注入 IHostedService?

问题

我应该如何将 DbContext 实例注入(使用标准依赖项注入)到 IHostedService

我试过什么

我目前让我的 IHostedService 类在构造函数中采用 MainContext(派生自 DbContext)实例。

当我运行应用程序时,我得到:

无法使用单例“Microsoft.Extensions.Hosting.IHostedService”中的范围服务“Microsoft.EntityFrameworkCore.DbContextOptions”。

所以我试图通过指定使 DbContextOptions 瞬态:

services.AddDbContext<MainContext>(options => 
                options.UseSqlite("Data Source=development.db"), ServiceLifetime.Transient);

在我的 Startup 课上。

但错误保持不变,即使根据 this solved Github issue,传递的 DbContextOptions 应该具有在 AddDbContext 调用中指定的相同生命周期。

我不能使数据库上下文成为单例,否则对它的并发调用会产生并发异常(由于不能保证数据库上下文是线程安全的)。

也许注入一个上下文工厂?例如stackoverflow.com/a/11748370/1663001

M
Martin Ullrich

在托管服务中使用服务的一个好方法是在需要时创建一个范围。这允许将服务/数据库上下文等与它们设置的生命周期配置一起使用。理论上,不创建范围可能会导致创建单例 DbContext 和不正确的上下文重用(EF Core 2.0 与 DbContext 池)。

为此,请注入一个 IServiceScopeFactory 并在需要时使用它来创建范围。然后从这个范围解决您需要的任何依赖项。如果您想将逻辑移出托管服务并仅使用托管服务来触发某些工作(例如,定期触发任务 - 这将定期创建范围,在这个范围也注入了一个数据库上下文)。

public class MyHostedService : IHostedService
{
    private readonly IServiceScopeFactory scopeFactory;

    public MyHostedService(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

    public void DoWork()
    {
        using (var scope = scopeFactory.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            …
        }
    }
    …
}

谢谢。注入 IServiceScopeFactory 和直接注入 IServiceProvider 有什么区别?
IServiceProvider 将只为您提供根服务提供商。虽然它还实现了创建范围的接口,但您可以使用它。但一般规则是要求尽可能少。
EF 提供的默认 AddDbContext() 方法仅将其注册为作用域。例如,在范围结束时,EF 将进行清理。您不希望在 Web 应用程序中使用单例数据库上下文,否则您的所有组件都会与其他组件的事务混淆。所有使用 db 上下文实例(通过构造函数注入)的服务也需要限定范围。
嗨@MartinUllrich,这真的很有用,谢谢!我在我的 BackgroundService 中使用了建议的模式,一切运行正常。但我仍然感到困惑:我的 DBContext 被包装在一个存储库中,我将存储库和 DBContext 作为 Program.cs 的 CreateHostBuilder 中的单例添加到服务集合中......是我的 BackgroundService 得到它的“自己的”单例存储库/DBContext通过注入的 IServiceScopeFactory?
@Peter 那应该给你同样的例子。但是,我建议您在此处使用作用域生存期(如果不可能,则使用瞬态),因为 EF Core / DbContext 不是线程安全的,并且从多个后台服务或控制器中使用它可能会导致问题
G
Ganesh Todkar

您可以在构造函数中添加创建范围,如下所示:

 public ServiceBusQueueListner(ILogger<ServiceBusQueueListner> logger, IServiceProvider serviceProvider, IConfiguration configuration)
        {
            _logger = logger;
            _reportProcessor = serviceProvider.CreateScope().ServiceProvider.GetRequiredService<IReportProcessor>();
            _configuration = configuration;
        }

做添加

using Microsoft.Extensions.DependencyInjection;

请注意,这将创建一个范围并且永远不会正确处置它。虽然默认实现 AFAIK 不会在服务被垃圾收集时处理该服务,但这可能是一个问题。这也意味着任何同样是 IDisposable 并在范围内注册的相关服务都不会被正确处理
修复它的一种方法是将服务范围捕获为一个字段,并通过在托管服务本身上实现 IDisposable 以释放创建的范围来释放它。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅