Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.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 (i.e. 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. (i.e. is Transient the best option for my UOW class? Why is it a better choice than Singleton in this situation? Etc.)

like image 312
Ben Walters Avatar asked May 11 '16 19:05

Ben Walters


People also ask

Can we use Entity Framework 6 in ASP.NET Core?

To use Entity Framework 6, your project has to compile against . NET Framework, as Entity Framework 6 doesn't support . NET Core. If you need cross-platform features you will need to upgrade to Entity Framework Core.

Is AddDbContext scoped?

The AddDbContext extension method registers DbContext types with a scoped lifetime by default.

Does .NET Core have dependency injection?

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

What is difference between Addtransient and Addscoped and Addsingleton?

Singleton is a single instance for the lifetime of the application domain. Scoped is a single instance for the duration of the scoped request, which means per HTTP request in ASP.NET. Transient is a single instance per code request.


1 Answers

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.

like image 170
Shadow Avatar answered Oct 24 '22 23:10

Shadow