2

I am really confused about which to choose scoped vs transient.

If I inject the DbContext as transient, I have the benefit that lets open many connections with SQL in one request.

For example

var clientCountTask = _clientRepo.CountAsync();
var orderCountTask =  _orderRepo.CountAsnyc();
....
await Task.WhenAll(clientCountTask,orderCountTask,...);

Also, I can handle the transaction through a "Unit-of-work" class, but first I should inform you that the unit of work is injected as scoped.

For example

await _unitOfWork.OrderRepo.AddAsync(order);
await _unitOfWork.Cart.ClearAsync();
await _unitOfWork.SaveChangesAsnyc();

For scoped the benefits that I found over transient are:

  1. uses less memory
  2. handles the transaction without the need to the unit of work class

The disadvantages for scoped are:

  1. I can't open more the one request to the database

For example

var clientCountTask = _clientRepo.CountAsync();
var orderCountTask =  _orderRepo.CountAsnyc();
....
await Task.WhenAll(clientCountTask,orderCountTask,...);

This throws an error:

A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

My questions are

  1. What are the disadvantages of using transient to inject my DbContext?

  2. When I should use scoped over transient?

  3. When I should use transient over scoped?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Tariq Hajeer
  • 308
  • 2
  • 14
  • 2
    Why *not* use it as scoped? And why use multiple instances? A DbContext already is a Unit-of-Work that lives as long as the scope. That's why it's not thread-safe. It doesn't have to be, it's not a connection. `AddAsync` isn't useful unless you use an exotic value generator like HiLo. `Add` doesn't insert anything to the database, it starts tracking detached objects in the `Added` state – Panagiotis Kanavos Jul 10 '23 at 18:26
  • 1
    `I can't open more the one request to the database` that's not a disadvantage of a scoped DbContext. It means the DbContext is used in a weird way. EF is an ORM, it tries to give the impression of working with in-memory objects. If that's not the use case, don't use an ORM. To get eg 2 counts at the same time you could use *one* query with 2 SELECTs and `Dapper` to map the results to separate values. – Panagiotis Kanavos Jul 10 '23 at 18:30
  • Related: https://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why – Steven Jul 10 '23 at 18:33
  • If you're using EF, IQueryable alone makes scoped the best choice! – GH DevOps Jul 10 '23 at 19:17

2 Answers2

1

If there are cases when you need several different instances of the context in the same scope - consider using DbContext factory approach:

services.AddDbContextFactory<ApplicationDbContext>(opts => ...);

It allows resolving both the context and when you need an instance - the factory, so you can construct an instance when needed:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

public MyService(IDbContextFactory<ApplicationDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

public void DoSomething()
{
    using (var context = _contextFactory.CreateDbContext())
    {
        // ...
    }
}

Notes:

  • The DbContext instances created in this way are not managed by the application's service provider and therefore must be disposed by the application.
  • For web applications there is some debate if it actually useful to query the database in parallel (at least as default approach, test if there are actual benefits in your concrete case).
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • 1
    Not just a debate and not just web apps. DBAs hate this because it means that instead of fixing inefficient queries, applications are running *more* of them causing even more trouble – Panagiotis Kanavos Jul 10 '23 at 18:31
0

For the most part in a web application in particular, a scoped DbContext instance is preferrable. Depending on how you structure your application, when you are breaking up operations across classes that might do things like return entities, using transient contexts can be more work / problematic since entities returned from one service that resolves it's own transient DbContext will not be tracked by the DbContext in the caller. So calling another class that returns instances cannot simply be associated to another entity you are populating. You cannot simply use Attach since this DbContext instance may or may not be already tracking a different entity instance with the same ID, and if you forget to associate it, you can end up with either constraint violations or duplicating data with new IDs as EF defaults to treating the entity as a new instance. (EF core may not do that duplicate data issue and instead complain about inserting a value without IDENTITY_INSERT=OFF)

There are certainly cases where it makes sense to use a more transient lifetime scope, where you want to execute something in the background, or simply perform an action without worrying about the DbContext becoming poisoned with un-persist-able changes, or persisting changes you don't want to commit yet. In these cases you can have the option to inject a DbContextFactory instead of, or alongside a Scoped DbContext. This factory can give code the option of scoping a transient instance, managing the lifetime with a using block. So it doesn't need to be an either-or. Another consideration where you just want to be able to isolate areas to avoid poisoning would be employing bounded DbContexts. This is essentially just splitting off entities into purpose-build DbContext definitions instead of a single DbContext that spans the entire domain. This has advantages for things like performance & isolating changes.

Another option is to use a unit of work that effectively wraps the DbContext entirely and manages its lifetime scope. Personally I use the DbContextScope UoW pattern by Mehdi el Gueddari originally written for EF6 which is maintained for EF Core:

https://github.com/zejji/DbContextScopeEFCore

... and available as a NuGet package. This is geared around facilitating things like a Repository pattern for testing. I cannot recommend this UoW enough as it allows you to scope the lifetime of one or more DbContext as you see fit where the Repositories (or DbContext consumers) can locate the scope to resolve a one or more DbContext instances rather than passing references around. You can also nest UoW scopes using the ForceCreateNew option. (Otherwise it will complain if you start accidentally scoping new UoW in calls already in a Scope as this is typically not expected)

Steve Py
  • 26,149
  • 3
  • 25
  • 43