Banana Cake
Banana Cake

Reputation: 1242

.NET Core Entity Framework - asynchronous writting into database

I have a problem. I have ASP .NET Core REST API app and in one mothod I"m trying to write more changes into a DB asynchronously. Everytime the different amount of objects is written into the DB and everytime one of three different errors ocured. Any suggestions what can be wrong?

This is my code:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connection_string), ServiceLifetime.Transient);
services.AddScoped<IHelper, Helper>();
...
}

Helper.cs

private MyDbContext _dbContext;
public Helper(IOptions<HelperSettings> settings, ILogger<Helper> logger, MyDbContext dbContext)
{
    ...
        _dbContext = dbContext;
    ...
}

public void Save(object entity)
{
    ...
        _dbContext.Add(entity);
}

This is the controller and the method that throws the exceptions.

public class MyController : ControllerBase
{
private readonly Helper _db;

public MyController(IHelper helper)
{
      _db = helper;
}
...

[HttpPost]
[Route("something")]
[Produces("application/json")]
public async Task<ActionResult<Guid>> CreateSomethingAsync([FromBody] DataRequest data)
{
    ...
          if (data.Answers != null)
          {
                List<Task> saveTasks = new List<Task>();

                foreach (AnswerData ans in data.Answers)
                {
                    Answer answer = ans.ConvertToAnswer(); //just create new Answer instance and filll it with data from AnswerData
                    saveTasks.Add(Task.Run(() => _db.Save(answer)));
                }

                await Task.WhenAll(saveTasks);
                await _db.DbContext.SaveChangesAsync();
          }
          return Ok(...);
}
}

I call the CreateSomethingAsync() in a cycle in another app. It throws one of this three exceptions:

System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'

or

System.InvalidOperationException: 'Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.'

or

System.InvalidOperationException: Cannot start tracking InternalEntityEntry for entity type 'Answer' because another InternalEntityEntry is already tracking the same entity

on the line _dbContext.Add(entity); in my Helper.cs.

I know that the problem is in the paralelism, but I dont know how to solve it. Any ideas?

Upvotes: 2

Views: 2110

Answers (1)

CodeCaster
CodeCaster

Reputation: 151690

DbContext is not thread-safe (that's what the exceptions you get are telling you), and calling DbContext.Set<T>.Add() does not take a significant amount of time. By parallelizing the Add() you are not adding multiple entities to the database asynchronously - you're just marking the entities as to-be-added upon calling SaveChanges().

So while I'm sure you have your reasons to parallelize your _db.Save(answer) calls, it probably has no performance improvements whatsoever, so you can remove it entirely, serializing the work.

If the work you do in there does benefit from parallelization, simply move the call to DbContext.Set<T>.Add() out of there - it is not thread safe.

Upvotes: 7

Related Questions