.Net 5 控制台后台服务异常,DI 为 Entity Framework Core

.Net 5 Console Background service exception with DI of Entity Framework Core

我有一个 .NET 5 控制台应用程序,我正尝试将其用作 Windows 服务,它现在以后台服务的形式出现。我有所有的依赖注入设置和 appsettings 和日志记录工作得很好。今天,我想将我的数据添加到组合中。这是我的应用程序的结构:

  1. 我有 Entity Framework 核心 DataContext class 全部完成。

  2. 然后我创建了一个包装器,我将使用它来创建我所有的 CRUD 方法并在我的应用程序中使用。这个 class 被命名为 DataAccess.

  3. 然后,我将以下内容添加到我的 Program.csMain 方法中:

    services.AddSingleton<DataAccess>();
    services.AddDbContext<MyDataContext>(options => options.UseSqlServer(ConnnectionString));
    
  4. 最后,在我的 DataAccess 包装器 class 中,我注入了 DataContext 以便我可以在这里进行 CRUD 操作。像这样:

     public DataAccess(MyDataContext dataAccess)
     {
         this.dataAccess = dataAccess;
     }
    

当我执行此操作并进行调试时,我的应用程序启动时出现以下异常:

Cannot consume scoped service 'ENS.Data.MyDataContext' from singleton 'ENS.Data.DataAccess'.

我知道我的 DataAccess 包装器是一个单例,我假设当您执行 services.AddDbContext 时,它是有范围的。所以,我有点理解这个错误。我不明白的是,我认为您可以从单例中使用作用域?

我在这里做错了什么或者更好的是,什么是设置和注入 Entity Framework 核心 DataContext 到我的应用程序以供我的 DataAccess 包装器使用的最佳方法,然后我的 DataAccess 包装器用于我的整个应用程序?

谢谢!

What I don't understand is I thought you could consume a scoped from a singleton?

那么解释如下:你想错了。你的想法其实没有意义。

  • DataAccess 是单例。永远只有一个。
  • 上下文被插入到构造函数中。

由于上下文可以有多个 - 这意味着第一个将被插入到数据访问中。你将永远无法使用另一个。

通常 - 除了 DataAccess 作为存储库是一个史诗般的反模式 - 注入数据库上下文是没有意义的,除非你只有一个 DataAccess 实例,然后你也可以或应该有一个单例上下文。

这个想法完全没有意义。现在,我会摆脱 DataAccess(或解雇你),但除此之外,你有两种方法来处理这个问题:

  • 接受您只有一个数据访问权限,向其中注入一个单例 dbcontext。
  • 如果需要范围,请将两者设为范围。

当您在另一个服务中注入一个服务时,您需要仔细考虑这两个服务的生命周期。如果将服务 A 注入服务 B,则服务 A 的生命周期必须大于或等于服务 B 的生命周期(因为服务 B 将持有服务 A 的引用,从而阻止垃圾收集器回收为服务分配的内存A).

基于此规则,您可以安全地将单例注入作用域服务内,但不允许相反(因为作用域服务的生命周期比单例服务的生命周期短)。违反此规则会导致称为 captive dependency 的反模式。如果允许将作用域服务注入单例服务,则作用域服务将成为俘虏依赖,其生命周期将与单例服务的生命周期一致(换句话说,注入的作用域服务实际上将成为单例)。

Microsoft DI 容器明确检查此反模式,这就是为什么您在尝试将作用域服务注入单例服务时遇到错误的原因。

在你的情况下你有两个选择:

  1. 避免数据访问包装器class并直接在需要的地方注入entity framework上下文
  2. 维护数据访问包装器 class 并给它一个作用域或瞬态生命周期,以便您可以将作用域 entity framework 上下文注入其中

作为旁注,如果您需要使用来自托管服务 class 的作用域服务,您可以在托管服务中注入 IServiceScopeFactory 服务并使用它来创建作用域并解析作用域服务。

这是一个例子:

public sealed class MyHostedService: BackgroundService 
{
  private readonly IServiceScopeFactory _scopeFactory;
  
  public MyHostedService(IServiceScopeFactory scopeFactory)
  {
    _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
  }
  
  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    // ...
  
    using (var scope = _scopeFactory.CreateScope())
    {
      var myScopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
      myScopedService.DoWork();
    }
    
    // ....
  }
}

您可以找到更多详细信息here