Как справиться с параллелизмом в EF Core

У меня есть метод, который ищет объект в базе данных и пытается создать его, если он не существует. Например:

public async Country FindOrCreate(string name)
{
    var country = _context.Countries.FirstOrDefault(p => p.Name == name);

    if (country != null)
        return country;

    country = new Country
    {
        Name = name
    };

    _context.Countries.Add(country);
    _context.SaveChanges();

    return country;
}

Проблема в том, что в фоновом режиме несколько задач одновременно вызывают FindOrCreate. Я предотвратил вставку дубликатов, создав уникальный индекс, но происходит следующее, вызывая нежелательные исключения:

  1. Задача A пытается найти страну и терпит неудачу
  2. Задача B пытается найти страну и терпит неудачу
  3. Задача A пытается создать страну и успешно
  4. Задача B пытается создать страну и терпит неудачу

Каким будет подходящий способ обработки этих сценариев параллелизма? Должен ли я работать с блокировками, используя код C#? Или я должен установить транзакцию? ТИА

Вероятно, вам следует нет вообще использовать ORM. То, что вы хотите сделать, может легко выполнить один INSERT ... WHERE.

Panagiotis Kanavos 30.01.2019 15:15

Страна — это ваша совокупность. Как утверждает DDD, вам не следует беспокоиться о сохранении инвариантов между агрегатами. В конечном итоге они должны быть согласованы. В противном случае вы получите не масштабируемую монолитную систему, полную взаимоблокировок. Другой вариант — создать еще один агрегат, который сгруппирует страны, т. е. Европу, Азию, и внедрить в него inforce-инварианты. Там вы можете легко применить пессимистичный или оптимистичный параллелизм. stackoverflow.com/questions/47180508/…

DmitriBodiu 30.01.2019 15:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
693
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Правильным подходом здесь является обработка параллелизма на уровне БД с использованием уникального индекса. Уникальный индекс гарантирует уникальность стран несмотря ни на что.

Не беспокойтесь о блокировке кода C#, он не будет работать, если у вас есть более 1 сервера, на котором запущено ваше приложение (в наши дни это вероятно). Транзакции в этом случае сложное дело, так что я бы не стал заморачиваться.

Как обрабатывать эти исключения:

Если ваша задача не удалась при создании страны, перехватите исключение и повторите попытку. Если по какой-то причине вам не удалось получить страну во второй раз, зарегистрируйте исключение и завершите работу.

Индекс гарантирует, что плохие данные не будут сохранены. Однако эта конкретная проблема не имеет ничего общего с транзакциями. ORM не предназначены для таких запросов. INSERT WHERE NOT EXISTS решит проблему, не требуя никаких транзакций и без каких-либо проблем с параллелизмом.

Panagiotis Kanavos 30.01.2019 15:11

может быть 2 одновременных запроса INSERT WHERE NOT EXISTS, где часть NOT EXISTS будет успешной, а один INSERT завершится ошибкой

Alex Buyny 30.01.2019 15:15

Нет, не будет. Не вставляется, но это не значит, что не получится

Panagiotis Kanavos 30.01.2019 15:16

Как так? Я бы подумал, что это приведет к нарушению уникального индекса?

Alex Buyny 30.01.2019 15:20

Почему? Предложение WHERE предотвратило бы любые вставки пока не не было соответствующей строки

Panagiotis Kanavos 30.01.2019 15:22
INSERT WHERE NOT EXISTS не является атомарным. пусть есть чат?
Alex Buyny 30.01.2019 15:27

Давайте продолжить обсуждение в чате.

Alex Buyny 30.01.2019 15:38

Привет, @AlexBuyny. Спасибо за ответ. Ваша идея как бы «сработала», но теперь мой код ведет себя очень странно. Я супер потерянный банкомат и расследование. По какой-то причине он генерирует исключение дублирующего индекса в верхнем методе после того, как правильный объект был найден и возвращен.

Vitor Durante 30.01.2019 15:55

Другие вопросы по теме