. NET Core Entity Framework - асинхронная запись в базу данных. - PullRequest
0 голосов
/ 13 февраля 2020

У меня проблема. У меня есть ASP. NET Базовое приложение API REST, и одним способом я пытаюсь записать больше изменений в БД асинхронно. Каждый раз в БД записывается различное количество объектов, и каждый раз возникает одна из трех разных ошибок. Любые предложения, что может быть не так?

Это мой код:

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

Это контроллер и метод, который выбрасывает исключения.

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

Я называю CreateSomethingAsync() в цикле в другом приложении. Он выбрасывает одно из этих трех исключений:

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

или

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.'

или

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

на линии _dbContext.Add(entity); в моем Helper.cs.

Я знаю, что проблема в паралелизме, но я не знаете, как ее решить. Есть идеи?

1 Ответ

5 голосов
/ 13 февраля 2020

DbContext не является поточно-ориентированным (именно об этом вам и сообщают исключения), и вызов DbContext.Set<T>.Add() не занимает значительного времени. Распараллеливая Add(), вы не добавляете несколько объектов в базу данных асинхронно - вы просто помечаете объекты как добавляемые при вызове SaveChanges().

Поэтому, хотя я уверен, что у вас есть причины для распараллеливания ваших вызовов _db.Save(answer), он, вероятно, не имеет никаких улучшений производительности, поэтому вы можете полностью удалить его, сериализовав работу.

Если работа Вы получаете пользу от распараллеливания, просто переместите вызов на DbContext.Set<T>.Add() - это не безопасно для потоков.

...