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

Sho*_*hoe 36 .net c# asp.net .net-core asp.net-core

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

我试过了什么

我目前让我的IHostedServiceMainContextDbContext构造函数中获取(派生自)实例.

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

无法从singleton'Microsoft.Extensions.Hosting.IHostedService'中使用作用域服务"Microsoft.EntityFrameworkCore.DbContextOptions".

所以我试着DbContextOptions通过指定来制作瞬态:

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

在我Startup班上

但是错误仍然是相同的,即使根据这个解决的Github问题,DbContextOptions传递的应该具有在AddDbContext调用中指定的相同生命周期.

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

Mar*_*ich 74

在托管服务中使用服务的一种好方法是在需要时创建范围.这允许使用服务/ db上下文等,并使用它们设置的生存期配置.理论上,不创建范围可能会导致创建单例DbContexts和不正确的上下文重用(EF Core 2.0与DbContext池).

为此,请注入IServiceScopeFactory并使用它在需要时创建范围.然后解决此范围内所需的所有依赖项.如果您希望将逻辑移出托管服务并使用托管服务仅触发某些工作(例如,定期触发任务 - 这将定期创建范围,创建任务服务),这也允许您将自定义服务注册为范围依赖项这个范围也会得到一个db上下文注入).

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>();
            …
        }
    }
    …
}
Run Code Online (Sandbox Code Playgroud)

  • EF 提供的默认 `AddDbContext()` 方法仅将其注册为作用域。例如,在范围结束时,EF 将进行清理。您不希望在 Web 应用程序中使用单例 db 上下文,否则您的所有组件都会与其他组件的事务混淆。所有使用 db 上下文实例(通过构造函数注入)的服务也需要被限定。 (10认同)
  • 谢谢。注入“IServiceScopeFactory”和直接注入“IServiceProvider”有什么区别? (7认同)
  • `IServiceProvider` 只会给你根服务提供者。虽然它还实现了创建作用域的接口,但您可以使用它。但一般规则是要求尽可能少。 (6认同)
  • @Peter 那应该给你同样的例子。不过,我建议您在此处使用作用域生命周期(如果不可能,则使用瞬态生命周期),因为 EF Core / DbContext 不是线程安全的,并且从多个后台服务或控制器使用它可能会导致问题 (2认同)
  • @MartinUllrich依赖“IDbContextFactory&lt;TContext&gt;”而不是容器/服务定位器会更好/更容易/更安全。你怎么认为?(也许当你写这个答案时这是不可能的......我看到它是[在 v5 中添加的](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.idbcontextfactory-1 #适用于)) (2认同)

Mar*_*der 5

DbContext对于从单例服务消费的特定情况

从 .NET 5 开始,您可以IDbContextFactory<TContext>为您的DbContext并将其注入到单例服务中。

用于AddDbContextFactory<TContext>注册工厂:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<MyDbContext>(
        options.UseSqlite("Data Source=development.db"));
}
Run Code Online (Sandbox Code Playgroud)

AddDbContext()注意:从 .NET 6 开始,您可以在使用时删除,AddDbContextFactory()因为后者还将上下文类型本身注册为作用域服务。

在单例服务中注入IDbContextFactory<TContext>并使用它来创建需要CreateDbContext()的实例:DbContext

public class MySingletonService : BackgroundService, IHostedService
{
    private readonly IDbContextFactory<MyDbContext> _contextFactory;

    public MySingletonService(IDbContextFactory<MyDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using (MyDbContext dbContext = _contextFactory.CreateDbContext())
        {
            await dbContext.MyData.ToListAsync(stoppingToken);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另请参阅
MS 文档:使用 DbContext 工厂(例如 Blazor)

备注
不要与以下内容混淆:
-IDbContextFactory<TContext>来自实体框架(4.3.1、5.0.0、6.2.0)。
-IDbContextFactory<TContext>来自实体框架核心(1.0、1.1、2.0、2.1、2.2)。