繁体   English   中英

无法在存储库构造函数 asp.net core 中创建服务范围

[英]cannot create scope of services in repository constructor asp.net core

我有瞬态存储库服务,每次调用它时都需要创建服务范围。

我尝试在存储库构造函数中创建此范围,如下所示:

public class ServiceRepository : IServiceRepository
{
    private IServiceScopeFactory _serviceScopeFactory;
    private IServiceScope _scope;
    private IServiceProvider _serviceContainer;

    private DataBaseContext _db;

    public ServiceRepository(DataBaseContext context, IServiceScopeFactory serviceScopeFactory)
    {
        _db = context;
        _serviceScopeFactory = serviceScopeFactory;
        _scope = _serviceScopeFactory.CreateScope();
        _serviceContainer = _scope.ServiceProvider;
    }

之后我尝试从服务提供商那里调用我的存储库服务:

var serviceRepository = _serviceProvider.GetRequiredService<IServiceRepository>();

我希望每次以这种方式调用此服务时,都会创建我在存储库构造函数中声明的服务范围。 但是在访问服务时,我收到错误:

System.InvalidOperationException: 'Cannot resolve 'Data_Access_Layer.Interfaces.IServiceRepository' from root provider because it requires scoped service 'Data_Access_Layer.EF.DataBaseContext'.'

我究竟做错了什么? 之前,我已经设置了这样的范围并且它起作用了:

var scopeFactory = _serviceProvider.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
var scopedContainer = scope.ServiceProvider;

但在这种情况下,我需要在每次调用 IServiceRepository 之前声明范围。 这就是为什么我想在 IServiceRepository 构造函数中声明作用域。

我为我提供帮助,只需将.UseDefaultServiceProvider(options => options.ValidateScopes = false)Program.cs BuildWebHos t中,如下所示:

public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .UseDefaultServiceProvider(options => options.ValidateScopes = false)
            .Build();

我希望这也会对某人有用

您正在使用所有这些错误。 首先,可以将范围内的服务直接注入瞬态生存期对象。 应该注入IServiceProviderIServiceScopeFactory等,而是实际的依赖。 已经直接注入了上下文(这是一种作用域服务),因此我不确定为什么要尝试以其他方式处理任何其他事情。

仅当对象具有单例生存期并且需要范围服务时,才应注入IServiceProvider (别无其他)。 这被称为服务定位器反模式,这是一个反模式,原因是:您应该避免尽可能多地执行此操作。 通常,大多数人认为应该是单身的人实际上不应该是单身的人。 在少数情况下,您真正​​需要单身人士的一生。 在所有其他情况下,“范围”应是您的终生目标。 另外,如果您的单例实际上需要范围服务,则强烈主张它应该实际上是范围内的。

但是,如果您确实处在真正需要单身人士生存期并且仍然需要范围服务的情况下,那么执行此操作的正确方法如下:

public class MySingletonService
{
    private readonly IServiceProvider _provider;

    public MySingletonService(IServiceProvider provider)
    {
        _provider = provider;
    }

    ...
}

就是这样。 您没有在构造函数内创建作用域。 从范围检索的任何服务仅存在于该范围内,并且当范围消失时,该服务也将存在。 因此,您无法将作用域服务持久保存到单例中的ivar。 相反,在需要这种服务的每个单独方法中,您需要执行以下操作:

using (var scope = _provider.CreateScope())
{
    var myScopedService = scope.ServiceProvider.GetRequiredService<MyScopedService>();
    // do something with scoped service
}

这就是服务定位器是反模式的另一个原因:它导致大量的晦涩和重复的代码。 有时您别无选择,但是大多数时候您都会选择。

虽然 Chris Pratts 的回答提供了“我如何将范围服务变成单例”的答案,但它并没有回答为什么您被告知您的存储库无法解析(至少在范围验证仍在进行时)。

什么是根提供者?

根提供程序是运行时用来通过它创建的范围来获取单例服务和所有其他服务的单例提供程序。 它的生命周期与应用程序生命周期直接相关。 如果您编写 Web api,只要您的应用程序启动,根提供程序就会存在。

调用 BuildServiceProvider 时会创建根服务提供者。 根服务提供者的生命周期对应应用程序/服务器的生命周期,当提供者与应用程序一起启动并在应用程序关闭时被销毁

什么是范围提供者?

范围提供程序用于创建……您猜对了,范围服务。 范围服务的生命周期与创建它的容器相关联。

范围服务由创建它们的容器处理

范围为您(开发人员)提供了一种定义特定服务生命周期的方法。 在 Web 项目中,范围的创建通常由请求管道处理,这是大多数场景所需要的。 框架在开始处理请求时为您创建一个范围,并使用该范围中的提供者来注入服务。 当请求完成时,作用域与其控制的服务一起被处理。 存在于许多 msdn 文档中的手动版本如下:

public void DoScopedWork(IServiceProvider serviceProvider)
{
  using (var scope = serviceProvider.CreateScope())
  {
    var scopedProvider = scope.ServiceProvider;
    var myService = scopedProvider.GetService<IMyService>();
    myService.DoWork();
  }
}

来自根提供者的范围服务?

出于某种原因,默认情况下,开发环境中的范围验证处于启用状态,这是其中之一,如果您正在阅读这篇文章,那么您已经做了一些错误的功能。

当 ValidateScopes 设置为 true 时,默认服务提供者会执行检查以验证:

  • 范围服务不是从根服务提供者直接或间接解析的
  • 范围服务不会直接或间接注入单身人士

因为作用域服务由创建它们的提供者在该提供者被释放(超出...范围?)时被释放,所以从根提供者创建一个作用域服务有效地创建了一个单例。

如果在根容器中创建了作用域服务,则该服务的生命周期将有效地提升为单例,因为它仅在应用程序/服务器关闭时由根容器处理。 调用 BuildServiceProvider 时,验证服务范围会捕获这些情况。

这些情况存在验证,因为在注册服务时,意图明确声明它应该是范围的。 因此,告诉框架IRepositoryService应该是范围的,然后告诉它也从根提供程序解析该范围的服务在术语上是矛盾的。 当然,根提供者可以创建此服务,因此可以关闭验证作用域的选项,但最好在确定这是正确的做法之前了解这可能对应用程序做什么。

为什么这对 OP 来说是个问题

var serviceRepository = _serviceProvider.GetRequiredService<IServiceRepository>();
@Giacomo,这对您来说是一个问题,根源在于您尝试使用存储库的位置。 无论您在代码中尝试解析存储库的哪个位置,都没有为您创建作用域提供程序。 您正在使用根提供程序。 如果没有关于您实际使用此存储库做什么的额外上下文,或者在您一生中这样做时,我可以说的是您可能需要首先using (var scope = _serviceProvider.CreateScope()) { ... }创建一个范围using (var scope = _serviceProvider.CreateScope()) { ... }并使用范围服务提供者来创建您的存储库。

您提到您的存储库应该是临时的,但这并不意味着您可以忽略作用域。 每次从提供者那里请求临时服务都是新的。 从根提供程序请求的瞬态服务仍将是单例。

正确,但不是真的

从技术上讲,您对问题的回答可能被认为是正确的,但它仍然没有了解正在发生的事情。 它没有解决实际问题,而是忽略警告并让范围服务 ( DataBaseContext ) 提升为有效的单例,因为它是由根提供程序创建的,以注入您的存储库(也是有效的单例)。 对于在您的情况下使用options.ValidateScopes = false ,我最多可以说它是一种解决方法。
旁注:让您的数据库上下文作为单例存在不是一个好主意

引用文件

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM