简体   繁体   中英

.NET Core/EF 6 - Dependency Injection Scope

I am currently working on setting up a .NET Core application using EF 6, and am having some trouble understanding the appropriate use of the various dependency registration methods. As I understand it:

  • Transient : Object is created when needed (ie a new instance every time requested)
  • Singleton : Single instance created at application start, and available for all following requests
  • Scoped : Available for the duration of a request

Specifically in my situation, I have set up a pair of DbContexts (based on the CQRS pattern) to handle database queries/commands that I'm registering as Scoped :

services.AddScoped((_) => new TestCommandContext(Configuration["Data:TestConnection:ConnectionString"]));
services.AddScoped((_) => new TestQueryContext(Configuration["Data:TestConnection:ConnectionString"]));

This is according to the ASP.NET Getting Started with ASP.NET 5 and Entity Framework 6 documentation:

Context should be resolved once per scope to ensure performance and ensure reliable operation of Entity Framework

I am then registering the respective UOW classes:

services.AddTransient<ITestCommandUnit, TestCommandUnit>();
services.AddTransient<ITestQueryUnit, TestQueryUnit>();

I am using Transient here based on this article, which suggests that:

Services registered with Transient scope are created whenever it is needed within the application. That means a new instance of the (registered service) class will be created by the dependency injection framework every time the (method in which the dependency is created) is executed.

Based on this understanding, I'm using registering my repository and service classes under Scoped as well:

services.AddScoped<ITestCommandRepository, TestCommandRepository>();
services.AddScoped<ITestQueryRepository, TestQueryRepository>();

services.AddScoped<ITestCommandService, TestCommandService>();
services.AddScoped<ITestQueryService, TestQueryService>();

Then calling my respective service layer methods in my controllers as needed:

public class TestController : BaseController
{
    private ITestQueryService testQueryService;

    // Get new object of type TestQueryService via DI
    public TestController(ITestQueryService testQueryService)
    {
        this.testQueryService = testQueryService;
    }

    [HttpGet]
    public IActionResult Edit(int id)
    {
        if (id > 0)
        {
            EditViewModel viewModel = new EditViewModel();
            viewModel.TestObject = testQueryService.GetById(id);
            return View(viewModel);
        }
        else
        {
            return RedirectToAction("Error", new { errorMessage = "No object with the specified Id could be found." });
        }
    }
}

In testing, this configuration appears to be working, and setting the DbContext(s) as Scoped makes sense - it seems unnecessary/inefficient to create a new context object every time it's requested.

However, the choice between Transient / Singleton / Scoped for the other objects is where I am lost. Can someone can help me understand the best configuration for this specific implementation of patterns?

The aforementioned setup is working, but I am looking for more understanding of why I should use the scopes I did. (ie is Transient the best option for my UOW class? Why is it a better choice than Singleton in this situation? Etc.)

Generally my rule of thumb is:

  1. Scoped - is way to go, saves cache and Your hair, because state is shared for entire request. No concurrency problems (all scoped services share single thread). Doesn't create instances if class is used multiple times in single request. If i do not know how class should be registered I go for scoped. Also usually You need something multiple times in single request - You can compute it once, and set value in field, so next queries to CreditLimit of your customer will not hit the datastore.

  2. Singleton is good for caches (server wide), config classes, objects designed with multiple threads in mind (multiple requests). Be aware that singleton should not have dependency on scoped objects. Also watch out for calling singletons in multiple threads. If You need singleton to do something in with request data pass it as function argument.

  3. Transient registration is very rare in my application. I use it for classes, that have internal state, and they can be used multiple times, and should not share that state. Usually utility or framework classes.

Example scoped class? SqlConnection - You don't want to open multiple connections to db from single request (because it is handled by connection pooling). Also service using that connection (service does one thing, so no need for multiple instances). Asp controller.

Example singleton? Most viewed articles today. Zip-code validator (no dependencies, could be singleton though).

Example transient? Think what would happen if all of Your Lists in that request shared state. List is not servicing request, but Your code, and may be used for different purposes during single request.

Keep in mind that if singleton has transient or scoped dependency it will not be disposed, until singleton is disposed (application recycle). Therefore scoped things can have dependency on singlestons, but singletons cannot have dependency on scoped.

Speaking of CQRS and DbContext - in my app I have single DbContext, shared by both Commands and Queries. Everything is registered per lifetime scope (Commands or Queries have no state kept after they finish, so they can be reused. Setting it as transient would work too). Another example is class that generates unique id for html elements. It is registered as scoped, and increments internal counter each time new id is queried. If class was transient, it would lost its state when called from next classes.

Be aware that some people have other points of view. If You use multiple lifetime scopes, it may be better to shift to transient dependencies. I like to pass factory if I need to use single dependency multiple times, and I try to have only one lifetime scope in my app.

  • Transient objects are always different; a new instance is provided to every controller and every service.
  • Scoped objects are the same within a request, but different across different requests
  • Singleton objects are the same for every object and every request (regardless of whether an instance is provided in ConfigureServices)

In your case, the service you re injecting does not depend on the state of other objects within the same request. You use the service to get an object within the Db. Either a transient or scoped service will work.

If in the same request you need to change the state of your object based upon calculations within the same request, you will need to use objects that live within the same request from the beginning until the end (ie: scoped).

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