3

I understand that DbContext is not thread-safe, plus DbContext caches data and it may leads to data inconsistency when several transactions try to save/commit their own changes into database. Thus, it is highly recommended to inject it per request (here). But I have a situation where only read operations exists (in a stand-alone class library) and there is no transaction or create/update/delete operations.
My question is: Is it safe to inject DbContext as singleton in this situation?

Rzassar
  • 2,117
  • 1
  • 33
  • 55
  • my question is: _why_? why not just let dependency injection do what it does best, and is doing so quite well for quite a long time now? what do you hope to gain by using singleton? – Franz Gleichmann Oct 28 '20 at 07:31
  • I'd say no, because while there are no database writes - EF itself writes data to it's internal structures while reading from database, and who knows how that behaves when multithreading, when library developers tell you explicitly library is not thread safe. – Evk Oct 28 '20 at 07:52
  • @FranzGleichmann Because 1) I have many many calls to that `dbContext` each time its methods gets called and I need to add `using(scope){create dbcontext()}` statement for each of them, which will pollute the code. 2) Each method is expected to be called on a **Really high volume** and instantiating `DbContext` 10 times for each calls could bring the server on to its knees (even when using DbContextPool). 3) Why you don't think the other way around? Why we don't inject it as a singleton (and cut off all the hassles related to scoped `DbContext`) when it is safe to use it this way? – Rzassar Oct 28 '20 at 08:00
  • @Evk "EF itself writes data to it's internal structures while reading from database" Would you please give me a link mentioned that? – Rzassar Oct 28 '20 at 08:04
  • No, but it's just common sense. You say yourself that `DbContext` caches data, and that is write operation (it means it writes that cached entity somewhere into internal structure so that it can return it later). Also there are many issues about thread safety in their github repo. For example here: https://github.com/dotnet/efcore/issues/18148 EF Core dev repeats once again: "DbContext is not thread-safe. The part of warning "EF Core does not support multiple parallel operations being run on the same context instance." is applicable to _every operation_ and not just "Saving" of records." – Evk Oct 28 '20 at 08:08
  • Since you have really high volume - what you need is likely caching layer. Then majority of requests will not hit database or EF at all. And if you are going to stick with EF without caching - especially in this situation you'll need to create context per operation and not one global per application, because mentioned cached entities will slow EF down over time, a lot. They will also stay in memory forever. – Evk Oct 28 '20 at 08:11
  • @Evk I believe that read operation doesn't change cache, but write operation does. As Steven mentioned in the link a provided in the question: "The DbContext contains a local cache of entities in your database. It allows you to make a bunch of changes and finally submit those changes to the database". Thus, I can safely conclude that there will be no cache utilization when there is no change. – Rzassar Oct 28 '20 at 08:28
  • @Evk Actually I'm using cache layer and I want `DbContext` to retrieve data for the first call only ;-D . Nevertheless, I don't want to pollute the code with `if(!alreadyCached) using(scope){create new dbcontext()}` statement. – Rzassar Oct 28 '20 at 08:32
  • You don't need your own lifecycle management using `using` statements, let the DI handle that for you. – CodeCaster Oct 28 '20 at 08:52

1 Answers1

6

Entity Framework developers explicitly say that DbContext is not thread safe for any operations performed on it, not just write (add, save changes etc) operations, and you should just believe them on that if you don't want to spend days debugging mysterious failures one day.

Even on read operations, EF can perform in-memory write operations on it's internal structures which are not thread safe, you cannot be sure it doesn't do that in any given case. For example, from documentation taking about processing of result set returned by a query:

If the query is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance

So if query is tracking query - it checks change tracker for current instance for already existing entity of this type with same key, which means if such entity doesn't exist - it puts it into change tracker. This is write operation, so not safe.

You can say, well, I'll just use AsNoTracking() then. But here is another issue, about conncurrent AsNoTracking queries - EF won't even allow you to execute them anyway. Library maintainer says:

Concurrent use of the same DbContext is not possible - and not just for tracked queries. Specifically, a DbContext has an underlying DbConnection to the database, which cannot be used concurrently. There are other various components at work under the hood which don't support multithreading.

However, there's nothing wrong with instantiating several DbContexts and executing queries on them - whether tracking or non-tracking. That should get you the behavior you're looking for. If you run into any further issues don't hesitate to post back.

So there are undocumented internal components in play which are not thread safe and you cannot be sure while doing anything on DbContext from multiple threads that you won't hit such components.

Evk
  • 98,527
  • 8
  • 141
  • 191