简体   繁体   English

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

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

I have transient-repository service, and I need to create scope of services every time I call it.我有瞬态存储库服务,每次调用它时都需要创建服务范围。

I've tried to create this scope in repository constructor like this:我尝试在存储库构造函数中创建此范围,如下所示:

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;
    }

and after that I tried to call my repository service from service provider:之后我尝试从服务提供商那里调用我的存储库服务:

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

I expect that every time I call this service this way, a scope of services will be created which I declared in the repository constructor.我希望每次以这种方式调用此服务时,都会创建我在存储库构造函数中声明的服务范围。 But when accessing the service, I get the error:但是在访问服务时,我收到错误:

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

What am I doing wrong?我究竟做错了什么? before, I've set the scope like this and it worked:之前,我已经设置了这样的范围并且它起作用了:

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

But in this case I need to declare scope every time before I'm calling IServiceRepository.但在这种情况下,我需要在每次调用 IServiceRepository 之前声明范围。 That is why I want to declare scope in IServiceRepository constructor.这就是为什么我想在 IServiceRepository 构造函数中声明作用域。

I helps for me just add .UseDefaultServiceProvider(options => options.ValidateScopes = false) to BuildWebHos t in Program.cs like this: 我为我提供帮助,只需将.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();

I hope it will be useful for someone too 我希望这也会对某人有用

You're using all of this wrong. 您正在使用所有这些错误。 First, transient lifetime objects can be injected with scoped services directly. 首先,可以将范围内的服务直接注入瞬态生存期对象。 You should not inject IServiceProvider or IServiceScopeFactory , etc., but rather, your actual dependencies. 应该注入IServiceProviderIServiceScopeFactory等,而是实际的依赖。 You're already injecting your context directly (which is a scoped service), so I'm not sure why you're attempting to handle any thing else in a different way. 已经直接注入了上下文(这是一种作用域服务),因此我不确定为什么要尝试以其他方式处理任何其他事情。

You should inject IServiceProvider (nothing else) only when your object has a singleton lifetime and needs scoped services. 仅当对象具有单例生存期并且需要范围服务时,才应注入IServiceProvider (别无其他)。 This is called the service locator anti-pattern, and it's an anti-pattern for a reason: you should avoid the need to do this as much as possible. 这被称为服务定位器反模式,这是一个反模式,原因是:您应该避免尽可能多地执行此操作。 In general, most of what people think should be singletons should actually not be singletons. 通常,大多数人认为应该是单身的人实际上不应该是单身的人。 There's only a handful of cases where you truly need a singleton lifetime. 在少数情况下,您真正​​需要单身人士的一生。 In all other scenarios, "scoped" should be your go-to lifetime. 在所有其他情况下,“范围”应是您的终生目标。 Additionally, if your singleton actually needs scoped services, that's a strong argument that it should actually be scoped itself. 另外,如果您的单例实际上需要范围服务,则强烈主张它应该实际上是范围内的。

However, if you do find yourself in a situation where you truly need a singleton lifetime and you still need scoped services, then correct way to do this is the following: 但是,如果您确实处在真正需要单身人士生存期并且仍然需要范围服务的情况下,那么执行此操作的正确方法如下:

public class MySingletonService
{
    private readonly IServiceProvider _provider;

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

    ...
}

And that's it. 就是这样。 You do not create a scope inside a constructor. 您没有在构造函数内创建作用域。 Any service retrieved from a scope only exists within that scope, and when the scope is gone, so is the service. 从范围检索的任何服务仅存在于该范围内,并且当范围消失时,该服务也将存在。 As such, you cannot persist scoped services to an ivar on a singleton. 因此,您无法将作用域服务持久保存到单例中的ivar。 Instead, inside each individual method that needs such a service, you need to do: 相反,在需要这种服务的每个单独方法中,您需要执行以下操作:

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

This is another reason why service locator is an anti-pattern: it leads to a lot of obtuse and repetitious code. 这就是服务定位器是反模式的另一个原因:它导致大量的晦涩和重复的代码。 Sometimes you have no choice, but most of the time you do. 有时您别无选择,但是大多数时候您都会选择。

While Chris Pratts answer provides the answer to "how do I get scoped services into a singleton" it is not an answer to why you are being told that your repository can't be resolved (at least when scope validation is still on).虽然 Chris Pratts 的回答提供了“我如何将范围服务变成单例”的答案,但它并没有回答为什么您被告知您的存储库无法解析(至少在范围验证仍在进行时)。

What is the root provider?什么是根提供者?

The root provider is the singleton provider that the runtime uses to source singleton services and all other services through scopes it creates.根提供程序是运行时用来通过它创建的范围来获取单例服务和所有其他服务的单例提供程序。 Its lifetime is tied directly to the application lifetime.它的生命周期与应用程序生命周期直接相关。 If your writing a web api, the root provider will exist as long as your app is up.如果您编写 Web api,只要您的应用程序启动,根提供程序就会存在。

The root service provider is created when BuildServiceProvider is called.调用 BuildServiceProvider 时会创建根服务提供者。 The root service provider's lifetime corresponds to the app/server's lifetime when the provider starts with the app and is disposed when the app shuts down根服务提供者的生命周期对应应用程序/服务器的生命周期,当提供者与应用程序一起启动并在应用程序关闭时被销毁

What are scoped providers?什么是范围提供者?

Scoped providers are used to create... you guessed it, scoped services.范围提供程序用于创建……您猜对了,范围服务。 The lifetime of a scoped service is tied to the container that created it.范围服务的生命周期与创建它的容器相关联。

Scoped services are disposed by the container that created them范围服务由创建它们的容器处理

A scope provides you, the developer, a way to define the lifetime of a particular service.范围为您(开发人员)提供了一种定义特定服务生命周期的方法。 In web projects, the creation of scope is typically handled by the request pipeline and that's all most scenarios need.在 Web 项目中,范围的创建通常由请求管道处理,这是大多数场景所需要的。 The framework creates a scope for you when it starts to handle the request and uses the provider from that scope to inject services.框架在开始处理请求时为您创建一个范围,并使用该范围中的提供者来注入服务。 When the request is complete, the scope is disposed of along with the services it controls.当请求完成时,作用域与其控制的服务一起被处理。 A manual version of this that exists in much of the msdn documentation would be as follows:存在于许多 msdn 文档中的手动版本如下:

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

Scoped services from the root provider?来自根提供者的范围服务?

Scope validation is on in Development environments by default for a reason, and it's one of those if-you're-reading-this-you've-done-something-wrong kind of features.出于某种原因,默认情况下,开发环境中的范围验证处于启用状态,这是其中之一,如果您正在阅读这篇文章,那么您已经做了一些错误的功能。

When ValidateScopes is set to true, the default service provider performs checks to verify that:当 ValidateScopes 设置为 true 时,默认服务提供者会执行检查以验证:

  • Scoped services aren't directly or indirectly resolved from the root service provider范围服务不是从根服务提供者直接或间接解析的
  • Scoped services aren't directly or indirectly injected into singletons范围服务不会直接或间接注入单身人士

Because scoped services are disposed of by the provider that created them when that provider is disposed (falls out of... scope?), creating a scoped service from the root provider effectively creates a singleton.因为作用域服务由创建它们的提供者在该提供者被释放(超出...范围?)时被释放,所以从根提供者创建一个作用域服务有效地创建了一个单例。

If a scoped service is created in the root container, the service's lifetime is effectively promoted to singleton because it's only disposed by the root container when app/server is shut down.如果在根容器中创建了作用域服务,则该服务的生命周期将有效地提升为单例,因为它仅在应用程序/服务器关闭时由根容器处理。 Validating service scopes catches these situations when BuildServiceProvider is called.调用 BuildServiceProvider 时,验证服务范围会捕获这些情况。

The validation exists for these cases because when the service was registered, the intent was explicitly stated that it should be scoped.这些情况存在验证,因为在注册服务时,意图明确声明它应该是范围的。 So, telling the framework IRepositoryService should be scoped and then telling it to also resolve that scoped service from the root provider is a contradiction in terms.因此,告诉框架IRepositoryService应该是范围的,然后告诉它也从根提供程序解析该范围的服务在术语上是矛盾的。 Of course the root provider CAN create this service, so the option to validate scopes CAN be turned off, but it's really best to understand what that could be doing to the app before deciding this is the right thing to do.当然,根提供者可以创建此服务,因此可以关闭验证作用域的选项,但最好在确定这是正确的做法之前了解这可能对应用程序做什么。

Why is this a problem for OP为什么这对 OP 来说是个问题

var serviceRepository = _serviceProvider.GetRequiredService<IServiceRepository>();
@Giacomo, this is a problem for you that is rooted in where you are trying to use the repository. @Giacomo,这对您来说是一个问题,根源在于您尝试使用存储库的位置。 Wherever in your code you are trying to resolve your repository is a place where a scoped provider hasn't been created for you.无论您在代码中尝试解析存储库的哪个位置,都没有为您创建作用域提供程序。 You are using the root provider.您正在使用根提供程序。 Without additional context about what you're actually doing with this repository, or when in the lifetime your doing it, what I can say is you are probably needing to create a scope first using (var scope = _serviceProvider.CreateScope()) { ... } and use the scoped service provider to create your repository.如果没有关于您实际使用此存储库做什么的额外上下文,或者在您一生中这样做时,我可以说的是您可能需要首先using (var scope = _serviceProvider.CreateScope()) { ... }创建一个范围using (var scope = _serviceProvider.CreateScope()) { ... }并使用范围服务提供者来创建您的存储库。

You mentioned that your repository is supposed to be transient, but this doesn't mean you get to ignore scope.您提到您的存储库应该是临时的,但这并不意味着您可以忽略作用域。 Transient services are new every time they're requested from the provider.每次从提供者那里请求临时服务都是新的。 A transient service requested from the root provider is still going to be a singleton.从根提供程序请求的瞬态服务仍将是单例。

Correct, but not really正确,但不是真的

Technically your answer to your question could be considered correct, but it still doesn't get at what is happening.从技术上讲,您对问题的回答可能被认为是正确的,但它仍然没有了解正在发生的事情。 Instead of fixing the actual problem it ignores the warning and lets the scoped service ( DataBaseContext ) be promoted to an effective singleton because it is being created by the root provider to be injected into your repository (also an effective singleton).它没有解决实际问题,而是忽略警告并让范围服务 ( DataBaseContext ) 提升为有效的单例,因为它是由根提供程序创建的,以注入您的存储库(也是有效的单例)。 The most I could say for using options.ValidateScopes = false in your case is that it is a workaround.对于在您的情况下使用options.ValidateScopes = false ,我最多可以说它是一种解决方法。
side note: it's not a good idea to let your db context exist as a singleton旁注:让您的数据库上下文作为单例存在不是一个好主意

Quoted Documentation 引用文件

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

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