Entity Framework Thread Safety

Entity Framework Thread Safety

More than one thread operating on a single Entity Framework context is not thread safe.

A separate instance of context for each thread is thread-safe. As long as each thread of execution has its own instance of EF context you will be fine.

In your example, you may call that code from any number of threads concurrently and each will be happily working with its own context.

However, I would suggest implementing a 'using' block for this as follows:

// this method is called from several threads concurrently
public void IncrementProperty()
{
using (var context = new MyEntities())
{
context.SomeObject.SomeIntProperty++;
context.SaveChanges();
}
}

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();
}

Is DbContext thread safe?

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

Thread safety with Entity Framework

This type of error occurs when you relate entities created in different instances of DbContext.

If repositories of UserService and CodeService have different contexts this error will occur at some point.

As you says in comments if you are using InstancePerLifetimeScope probably contexts of each service are different because the repositories are created in different scopes.

You should use InstancePerRequest to ensure that the context is the same throughout the execution of the entire request.

UnitOfWork and DbContext: thread safety with DI

The source of the problem is that MyContext is held captive as a Captive Dependency in the following object graph:

MqttClientHostedService
-> PositionService
-> UnitOfWork
-> MyContext

All types in this graph are registered as Transient, but still, services that act as hosted service (e.g. your MqttClientHostedService) are resolved only once for the duration of the application and cached indefinately. This effectively makes them a singleton.

In other words, MyContext is accidentally kept alive by the single MqttClientHostedService and because multiple messages can come in in parallel, you have yourself a race condition.

The solution is to let each ApplicationMessageReceived event run in its own unique little bubble (a scope) and resolve a new IPositionService from within that bubble. For instance:

public class MqttClientHostedService : IHostedService, IDisposable
{
[...]
public MqttClientHostedService(
ILogger<MqttClientHostedService> logger,
IOptions<MqttClientConfiguration> mqttConfiguration,
IServiceProvider provider)
{
this.logger = logger;
this.config = mqttConfiguration;
this.provider = provider;
}
[...]
private async Task MqttClient_ApplicationMessageReceived(
object sender, MqttApplicationMessageReceivedEventArgs e)
{
using (var scope = provider.CreateScope())
{
positionService = scope.ServiceProvider
.GetRequiredService<IPositionService>();
string message = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
await positionService.HandleMessage(message);
}
}

[...]
}

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();
});
}
}


Related Topics



Leave a reply



Submit