简体   繁体   中英

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. That is why I want to declare scope in IServiceRepository constructor.

I helps for me just add .UseDefaultServiceProvider(options => options.ValidateScopes = false) to BuildWebHos t in Program.cs like this:

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. 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. 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. 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).

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.

The root service provider is created when BuildServiceProvider is called. 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. 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:

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:

  • 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.

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. 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

var serviceRepository = _serviceProvider.GetRequiredService<IServiceRepository>();
@Giacomo, this is a problem for you that is rooted in where you are trying to use the repository. 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.

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). The most I could say for using options.ValidateScopes = false in your case is that it is a workaround.
side note: it's not a good idea to let your db context exist as a singleton

Quoted Documentation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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