3

When I update the entity, I got the error in the controller

A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

Code:

public class MyController: Controller
{
   private readonly DbContext _db = new DbContext();

The method is

[HttpPatch]
[Route("MyRoute")]
public async Task<ActionResult> UpdateMyCase([Required][FromBody]MyProject body)
{
    using(var dbContextTransaction = _db.Database.BeginTransaction())
    {
        _db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        var p = (from a in _db.MyProject
                 where a.Id == body.Id
                 select a).FirstOrDefault();
        p.Name = "new Name";
        p.Score = "new score";

        // ....
        var m = _db.MyProjectLink.Where(x => x.Id == p.Id);

        for(var key in m)
        {
            if(m.Any(x => x.Id != "something"))
            {
                var link = new MapProjectLink();
                link.MapId = "some id dynamic generated";
                link.Id = body.Id;
                link.Tool = key.tool;
                _db.MapProjectLink.Add(link);
            }
        }

        await _db.SaveChangesAsync();
        return OK(p);
    }
}

To explain the code, basically I have three tables. _db.MyProject, _db.MyMap and _db.MapProjectLink. The first two tables are many to many; and the third table links them together. I want to save the updated value to the two tables: _db.MyProject and _db.MapProjectLink.

By the way I don't use dependency injection at this moment. I guess that maybe the for loop causes the problem.

The error is

An exception occurred in the database while saving changes for context type 'MapProjectLink'. System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Hello
  • 796
  • 8
  • 30
  • I feel like I read something about not declaring transactions for EF because it handles that under the hood itself, I'll see If i can find something on this. – nulltron Oct 09 '19 at 01:08
  • This may be a duplicate: https://stackoverflow.com/questions/48767910/entity-framework-core-a-second-operation-started-on-this-context-before-a-previ – nulltron Oct 09 '19 at 01:12
  • I read that but no big help for my specific case. – Hello Oct 09 '19 at 01:42
  • does `await _db.SaveChangesAsync();` utilize multithreading to make database writes? Could that be why its concerned about thread safe? – nulltron Oct 09 '19 at 01:51
  • while `using(var dbContextTransaction = _db.Database.BeginTransaction())` should you still use the async context save? – nulltron Oct 09 '19 at 01:54
  • I used to use `SaveChanges`, samething so I switch to `await _db.SaveChangesAsync()` but it doesn't help. – Hello Oct 09 '19 at 01:56

3 Answers3

1

It's turns out I have to put everything associated with _db into Task.Run. Then Wait. Which means wait the task finish then continue to the next flow.

Hello
  • 796
  • 8
  • 30
0

DbContext is not thread-safe. You should create a new context (rather then re-using the same _db instance for each database operation. This is a best-practice even for single-threaded processes.

Side note - that's why when you wait for each task it "works" - because you are no longer accessing your context from multiple threads.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • I thought about it. But just don't know how to wrap it in code. Notice, I have `private n DbContext _db = new DbContext();` at the beginning. – Hello Oct 10 '19 at 01:21
  • Just copy that to the spots where you use the context (and wrap it in a `using` so it is disposed) : `using (DbContext db = new DbContext()) {`. Yes it's repetitive but it's the right way to use it. – D Stanley Oct 10 '19 at 01:51
-1

I think you are missing to commit the transaction at the end....dbContextTransaction.Commit() after SaveChangesAsync() should do the trick.

devendra
  • 79
  • 2