Is Dbcontext Thread Safe

Is DbContext thread safe?

It's not thread safe. Simply create a new instance of DbContext in you thread.

Is a DbContext per thread in Parallel.ForEach safe?

Your pattern is thread-safe. However, at least for SQL Server, if your concurrency is too high, you'll find that your total throughput drops off as contention for database resources increases.

In theory, Parallel.ForEach optimizes the number of threads, but in practice, I have found it allows too much concurrency in my applications.

You can control concurrency with the ParallelOptions optional parameter. Test your use case and see if the default concurrency works well for you.

Your comment: Keeping in mind that, right now anyway, that we are talking about 100's of ids in that code above where most id's represent work that does not end up in any changes to the database and are short lived while a hand full can take minutes to complete and ultimately add 10's of new records to the DB. What MaxDegreesOfParallelism value would you recommend off the top of your head?

Probably 2-3 based on your general description, but it depends on how database intensive ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords is (vs. performing CPU-bound activities or waiting for IO from files, web service calls, etc). With more than that, if that method is mostly performing DB tasks, you're likely to get locking contention or overwhelm your IO subsystem (disks). Test in your environment to be sure.

It may be worth exploring why ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords is taking so long to complete for a given Id.

UPDATE

Here's some test code to demonstrate that the threads do not block each other and indeed run concurrently. I removed the DbContext portion for simplicity and since it doesn't affect the threading issue.

class SomeTaskProcessor
{
static Random rng = new Random();
public int Id { get; private set; }
public SomeTaskProcessor(int id) { Id = id; }
public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
{
Console.WriteLine($"Starting ID {Id}");
System.Threading.Thread.Sleep(rng.Next(1000));
Console.WriteLine($"Completing ID {Id}");
}
}
class Program
{
static void Main(string[] args)
{
int[] ids = Enumerable.Range(1, 100).ToArray();

Parallel.ForEach(ids, id => {
var processor = new SomeTaskProcessor(id);
processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
});
}
}

Entity Framework DbContext and thread safety

Simple way is, to have one DbContext per request, ASP.NET MVC does all thread safety, each controller instance in ASP.NET MVC is isolated for every request, you don't have to worry about race conditions. As long as you don't create threads and just simply do data transformation in action method using single DbContext, you will not have any problem.

Basically DbContext does nothing, it just queues SQL query to target database, it is the database which handles multi threading, race conditions. To protect your data, you should use transactions and add validations in your database to make sure they are saved correctly

public abstract class DbContextController : Controller{

public AppDbContext DB { get; private set;}

public DbContextController(){
DB = new AppDbContext();
}

protected override void OnDisposing(bool disposing){
DB.Dispose();
}

}

If you inherit any class from DbContextController and use DB throughout the life of controller, you will not have any problem.

public ActionResult ProcessProducts(){
foreach(var p in DB.Products){
p.Processed = true;
foreach(var order in p.Orders){
order.Processed = true;
}
}
DB.SaveChanges();
}

However, if you use any threads like in following example,

public ActionResult ProcessProducts(){
Parallel.ForEach(DB.Products, p=>{
p.Processed = true;
// this fails, as p.Orders query is fired
// from same DbContext in multiple threads
foreach(var order in p.Orders){
order.Processed = true;
}
});
DB.SaveChanges();
}

DbContext & DbcontextPool in ef-core

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.
  • "Dbcontext is safe from concurrent access issues in most ASP.NET Core applications": This text is a bit misleading, because it might make the reader believe that DbContext instances are thread-safe, but 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, which would make their use 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 accross 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 noticable. 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.

EF core DbContext in a multithreaded API application

You should create a scope whenever your TimedHostedServices triggers.

Inject the service provider in your constructor:

public MyServiceService(IServiceProvider services)
{
_services = services;
}

and then create a scope whenever the task triggers

using (var scope = _services.CreateScope())
{
var anotherService = scope.ServiceProvider.GetRequiredService<AnotherService>();

anotherService.Something();
}

A more complete example is available in the doc

Is it ok to inject DbContext as singleton when there is only read operations?

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.

Async searching with DbContext in a thread-safe way


I subscribe to PropertyChanged and when this is fired, I call my repository classes to perform an async database search... despite "await"ing every asynchronous call to the database, if I type too quickly I get "A second operation was started on this context before a previous operation completed" on the DbContext class.

My best guess is that because my initial call to the DB Repository's search function is contained in an event, and that event is firing in the UI thread, it's "Overriding" the fact that I'm using await and causing a second query to run. I either need a way of stopping the first query, or a way of ensuring that the await is honoured.

This is one of the problems with async void: it's not easy for calling code to know when the asynchronous method has completed. It's the async void PropertyChanged that is actually causing the problem.

Any suggestions for making this work well with ToListAsync(), but sill allowing the UI to feel responsive?

First, I recommend debouncing as others have commented. That will reduce unnecessary db calls, although it won't fix this problem. For a proper fix, you should either have a pool of db contexts that your code allocates from (and returns to after the operation is complete), or just use a SemaphoreSlim. If you have one shared SemaphoreSlim (the same place where your existing Context is), and call await semaphore.WaitAsync(); before the db operations and semaphore.Release() after the db operations, then that ensures one-at-a-time access:

await ContextMutex.WaitAsync();
try
{
result = await Context.Where(p => (p.Surname == surname)
.OrderBy(p => p.Surname)
.Take(maxResults / sString.Length).ToListAsync();
}
finally
{
ContextMutex.Release();
}

I am just switching over to async now.

One nice feature of SemaphoreSlim is that it also works with synchronous code; you can have that code call Wait instead of await WaitAsync. You should apply the mutual exclusion to all accesses of Context to ensure correctness.



Related Topics



Leave a reply



Submit