Является ли собственная область действия плохим решением для одновременного доступа к DbContext?

Я столкнулся с этой ошибкой в ​​одноэлементной службе, когда два экземпляра одного и того же класса пытались использовать один и тот же метод, который использует репозиторий.

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
   at Npgsql.EntityFrameworkCore.PostgreSQL.Update.Internal.NpgsqlModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple`2 parameters, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1 entriesToSave, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

Чтобы решить эту проблему, я нашел решение, используя собственную область видимости в Autofac.

private readonly Func<Owned<IProductRepository>> productRepositoryFactory;
...
using (var repositoryOwned = productRepositoryFactory())
{
   await repositoryOwned.Value.DeleteRangeAsync(idsToRemove);
}

Это решило проблему, но я боюсь, что производительность будет снижена из-за спама при создании и удалении DbContext (используемый DbContext регистрируется в зависимости от зависимости). Есть ли лучшее решение этой проблемы, которое не повлияет на производительность таким образом?


person pope_yol    schedule 05.02.2020    source источник
comment
Не существует такой вещи, как одновременный доступ к DbContext. Класс не предназначен для одновременного доступа   -  person Panagiotis Kanavos    schedule 05.02.2020
comment
DbContext также не является подключением к базе данных. Соединение открывается только при необходимости, и даже в этом случае пул соединений означает отсутствие штрафа за закрытие соединения сразу после использования. Напротив, сохранение соединения открытым приводит к увеличению задержек, поскольку любые блокировки, полученные при открытом соединении, остаются до его закрытия. Через некоторое время соединения начинают конфликтовать друг с другом.   -  person Panagiotis Kanavos    schedule 05.02.2020
comment
Теперь я заметил, что на самом деле ошибка DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s).. Это совсем не касается многопоточности или одновременного доступа к DbContext. Речь идет об оптимистичном параллелизме. Это означает, что строка, которую DbContext пытался сохранить, тем временем была изменена. Это бизнес проблема, ее нельзя исправить с помощью AutoFac или какого-либо решения по умолчанию.   -  person Panagiotis Kanavos    schedule 05.02.2020
comment
Это похоже на git конфликты. Вы должны решить, что вы хотите сделать в этом случае - перезаписать изменения? В некоторых случаях это нормально. Отменить собственные изменения? Загрузить измененные данные и проверить, какие строки были изменены? Изменения в столбцах аудита, например ModifiedBy, можно безопасно игнорировать. Или вам следует спросить конечного пользователя, что ему делать?   -  person Panagiotis Kanavos    schedule 05.02.2020
comment
В документации EF Core объясняется, как работают конфликты параллелизма и как их разрешите их. Вопрос как есть не содержит достаточно информации, чтобы сказать, что следует делать. Код Autofac не имеет значения. В исключении даже не упоминаются сущности. Как выглядит класс Product, как он настроен? Каким образом два разных соединения привели к изменению одного продукта? Это действующий бизнес-сценарий? Могут ли два пользователя открыть одну и ту же страницу продукта и изменить описание или название?   -  person Panagiotis Kanavos    schedule 05.02.2020
comment
@PanagiotisKanavos У меня есть 4 отношения к базе данных: Product, Category, ProductCategory, ProductCategoryHistory. Несмотря на то, что Продукт может быть назначен максимум одной Категории одновременно (ограничение в базе данных), у нас все еще есть отдельная связь для этого. Также мы храним историю этого задания для статистики. Чтобы изменить категорию продукта, вам необходимо удалить назначение, добавить удаление в историю, добавить новое назначение, добавить дополнение в историю. Проблема возникает, когда один человек удаляет категорию, а второй меняет категорию.   -  person pope_yol    schedule 05.02.2020
comment
Я хотел бы сделать так, чтобы изменение имело приоритет перед удалением, но оно вылетает на этапе удаления (при изменении требуется 4 шага, как описано выше)   -  person pope_yol    schedule 05.02.2020
comment
Это не вопрос приоритета - запись уже удалена. Он также не дает сбоев - он сообщает вам о конфликте параллелизма. Вы должны уладить конфликт и либо повторить попытку внесения изменений, либо отменить их, либо объединить их, либо спросить пользователя. the change has priority over the removal означает ли это, что вы хотите воссоздать удаленный объект? Это .... странно, если не сказать больше. Этот DELETE, вероятно, также удалил записи связующего. В любом случае исправление в этом случае состоит в том, чтобы вставить удаленные записи снова, если это возможно.   -  person Panagiotis Kanavos    schedule 06.02.2020
comment
Если вы хотите повторно вставить уже удаленные записи, у вас есть более серьезная проблема - насколько это оправдано в бизнес-процессе? Как правило, записи не удаляются - их статус меняется на Completed, inactive, cancelled или что-то другое, имеющее бизнес-смысл. Когда что-то удаляется из базы данных, это значит, что оно исчезнет без следа. Если вы попали в ситуацию, когда вам нужно воскресить удаленную запись, что-то точно не так   -  person Panagiotis Kanavos    schedule 06.02.2020
comment
Я имел в виду, что два разных пользователя пытаются удалить одну и ту же запись в базе данных, а не воссоздавать ее при изменении, когда применяется изменение, создается другая запись. Насколько я понял, я должен просто проигнорировать ошибку actually affected 0 row(s) и повторить транзакцию без нее, потому что мне все равно, что запись, которую я хотел удалить, уже удалена.   -  person pope_yol    schedule 06.02.2020
comment
@PanagiotisKanavos Я прав или мне стоит попробовать изменить подход?   -  person pope_yol    schedule 06.02.2020
comment
Или, если я хочу перейти на пессимистичный параллелизм, как я могу подойти к этому в случае удаления?   -  person pope_yol    schedule 06.02.2020
comment
Вы читали связанные документы? В этой статье объясняется, что происходит и что вы можете сделать. Объект исключения уже содержит старое и новое значения, поэтому вы можете проверить, что произошло. Вам не нужно переключаться на пессимистичный параллелизм.   -  person Panagiotis Kanavos    schedule 06.02.2020
comment
Спасибо за помощь. Думаю, я понял, как это исправить, но мне нужно протестировать это в контексте проекта и проверить общий подход к параллелизму в проекте. Я не могу отметить ваш комментарий как ответ, чтобы поблагодарить вас. Можете ли вы добавить маркерный ответ, чтобы я мог указать вам?   -  person pope_yol    schedule 10.02.2020


Ответы (1)


Создание области - единственный способ, если вы планируете передать контекст асинхронному методу. Например, вы можете использовать DI для IServiceProvider serviceProvider в конструкторе, а затем использовать его следующим образом:

 using (var scope = _serviceProvider.CreateScope())
 {
     var service = scope.ServiceProvider.GetRequiredService<IYOURSERVICE>();
     ...
 }
person Azzy Elvul    schedule 05.02.2020
comment
Исключение вовсе не касается одновременного доступа. Речь идет об оптимистичном параллелизме - person Panagiotis Kanavos; 05.02.2020