Search code examples
multithreading.net-coredependency-injectionentity-framework-coredbcontext

DbContext & DbcontextPool in ef-core


I read a lot of documents and articles about DBcontext in Efcore and its lifetime, however, I have some questions.

Based on this link "https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/" the best lifetime of DBcontext and default lifetime of AddDbContext is scope, but there is a contradiction in the two below sentences on this document.

"DbContext is not thread-safe. Do not share contexts between threads. Make sure to await all async calls before continuing to use the context instance."

on the other hand, it was mentioned too,

"Dbcontext is safe from concurrent access issues in most ASP.NET Core applications because there is only one thread executing each client request at a given time, and because each request gets a separate dependency injection scope (and therefore a separate DbContext instance)."

  1. I was confused about whether registering DBcontext as a scoped service is thread-safe or not?
  2. What are the problems of registering DBcontext as a singleton service in detail?

In addition, I read some docs that prohibit registering singleton DbContext, however, AddDbContextPool makes to register singleton DBcontext. so there are some questions about the Dbcontextpool.

  1. what are the impacts of using the DbContextPool instead of the DbContext?
  2. when we should use it and what should be considered when we use contextPool?
  3. DbContextPool is thread-safe?
  4. Has it memory issues because of storing a number of dbset instances throughout the application's lifetime?
  5. change-tracking or any parts of the ef would be failed or not in the DB context pool?

One DbContext per web request... why?

.NET Entity Framework and transactions


Solution

  • I understand why you think the language in the Microsoft documents is confusing. I'll unravel it for you:

    • "DbContext is not thread-safe." This statement means that it's not safe to access a DbContext from multiple threads in parallel. The stack overflow answers you already referenced, explain this.
    • "Do not share contexts between threads." This statement is confusing, because asynchronous (async/await) operations have the tendency to run across multiple threads, although never in parallel. A simpler statement would be: "do not share contexts between web requests," because a single web request typically runs a single unit of work and although it might run its code asynchronously, it typically doesn't run its code in parallel. Another way of putting it is "do not access a single DbContext instance in parallel," which is what will happen if a single DbContext is shared between (web) requests.
    • "Dbcontext is safe from concurrent access issues in most ASP.NET Core applications": This text is misleading, because it might make the reader believe that DbContext instances are thread-safe, which they aren't. What the writers mean to say here is that, with the default configuration (i.e. using AddDbContext<T>(), ASP.NET Core ensures that each request gets its own DbContext instance, making it, therefore, "safe from concurrent access" by default.

    1 I was confused about whether registering DBcontext as a scoped service is thread-safe or not?

    DbContext instances are by themselves not thread-safe, which is why you should register them as Scoped, because that would prevent them from being accessed from multiple requests, and thus in parallel.

    This doesn't prevent you from spinning of (background) operations from within a single request all accessing the DbContext instance in parallel. This could happen when you use Parallel.ForEach or when you forget to await an asynchronous operation (causing it to run in parallel in the background). These types of situations should be prevented because, again, DbContext is not thread-safe.

    2 What are the problems of registering DBcontext as a singleton service in detail?

    This is already described in detail in this answer, which you already referenced. I think that answer goes into a lot of detail, which I won't repeat here.

    In addition, I read some docs that prohibit registering singleton DbContext, however, AddDbContextPool makes to register singleton DBcontext. so there are some questions about the Dbcontextpool.

    The DbContext pooling feature is very different from registering DbContext as singleton, because:

    • The pooling mechanism ensures that parallel requests get their own DbContext instance.
    • Therefore, multiple DbContext instances exist with pooling, while only a single instance for the whole application exists when using the Singleton lifestyle.
    • Using the singleton lifestyle, therefore, ensures that one single instance is reused, which causes the myriad of problems laid out (again) here.
    • The pooling mechanism ensures that, when a DI scope ends, the DbContext is 'cleaned' and brought back to the pool, so it can be reused by a new request.

    what are the impacts of using the DbContextPool instead of the DbContext?

    More information about this is given in this document.

    when we should use it and what should be considered when we use contextPool?

    When your application requires the performance benefits that it brings. This is something you might want to benchmark before deciding to add it.

    DbContextPool is thread-safe?

    Yes, in the same way as registering a DbContext as Scoped is thread-safe; in case you accidentally hold on to a DbContext instance inside an object that is reused across requests, this guarantee is broken. You have to take good care of Scoped objects to prevent them from becoming Captive Dependencies.

    Has it memory issues because of storing a number of dbset instances throughout the application's lifetime?

    The memory penalty will hardly ever be noticeable. The so-called first-level cache is cleared for every DbContext that is brought back to the pool after a request ends. This is to prevent the DbContext from becoming stale and to prevent memory issues.

    change-tracking or any parts of the ef would be failed or not in the DB context pool?

    No, it doesn't. For the most part, making your DbContext pooled is something that only requires infrastructural changes (changes to the application's startup path) and is for the most part transparent to the rest of your application. But again, make sure to read this to familiar yourself with the consequences of using DbContext pooling.