SQL позволяет вставлять повторяющиеся данные, когда две вставки выполняются почти одновременно

Контекст: у меня есть приложение в Azure, которое получает информацию от нескольких клиентов, однако иногда клиент отправляет несколько сообщений одновременно, что, по-видимому, приводит к дублированию записей в моей базе данных.

В сообщении есть externalid (идентификатор на стороне клиента), customerId (идентификатор клиента) и еще один subobject с некоторыми значениями (например: высота, цена и т. д.).

При каждом изменении, внесенном пользователем в этот объект или его подобъект, на наш сервер отправляется сообщение. Этими изменениями могут быть: создание, редактирование и исключение. Но иногда пользователь повторно отправляет это сообщение, что, похоже, в некоторых случаях создает второй регистр на моей стороне.

Теперь я делаю проверки, чтобы избежать этого:

var FoundCustomer = _dataContext.Customer
                                .Where(x => x.CustomerId == message.customerId)
                                .SingleOrDefault();

if (FoundCustomer != null)
{
    var Order = _dataContext.Order
                            .Where(x => x.Customer == FoundCustomer)
                            .SingleOrDefault(x => x.ExternalId == message.externalId);

    if (Order == null)
    {
        var OrderNew = new Order
                           {
                               Externalid = message.externalId, 
                               Customer = FoundCustomer
                           };

        await _dataContext.Order.AddAsync(OrderNew);
        await _dataContext.SaveChangesAsync();
    }
}

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

Я что-то пропустил?

Убедитесь, что await нигде не забыт.

Alexander Petrov 12.07.2024 17:46

Примечание: используйте SingleOrDefaultAsync.

Alexander Petrov 12.07.2024 17:47

Вы используете HiLo? Если нет, то используйте синхронный Add.

Alexander Petrov 12.07.2024 17:49

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

Andrew Morton 12.07.2024 17:49

@AlexanderPetrov, не будет ли забытое «ожидание» поймано компилятором? Кроме того, не могли бы вы объяснить, почему использовать SingleOrDefaultAsync, а не для добавления? Нет, я использую Microsoft.EntityFrameworkCore.

Fernando 12.07.2024 19:19

@AndrewMorton, наверное. Но я не могу это контролировать, это стороннее приложение.

Fernando 12.07.2024 19:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
73
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам следует выделить входящие заказы в отдельную таблицу и считать их дополнительным заданием. Таким образом, вы также можете убедиться, что внешний идентификатор еще не был прочитан, и закрыть дубликат как таковой. Это также помогает, когда вы хотите отключить контекст заказа, чтобы служба могла читать заказы извне. Это действительно требует немного больше усилий, но по моему опыту работы в крупной компании электронной коммерции я могу сказать, что это дает вам лучший контроль.

Это нехорошее решение. Как я уже говорил в вопросе, у меня есть подобъект, который идет вместе с ним, поэтому мне понадобится еще одна таблица, чтобы связать заказ с клиентом. Было бы идеально отложить второй SingleOrDefault(), чтобы AddSync() и SaveChangesAsync() могли выполниться первыми и таким образом найти дубликат. Кроме того, второе сообщение может отличаться от первого (в очень редких случаях пользователь меняет порядок, и возникают проблемы с подключением, из-за которых отправляются два сообщения от одного и того же пользователя, но в разном порядке), поэтому мне нужно обновить первая запись, соответствующая новому заказу.

Fernando 17.07.2024 20:26
Ответ принят как подходящий

Я обнаружил, что моя проблема — это просто старая проблема с названием «состояние гонки», каждое сообщение запускало функцию, создавая экземпляр, вызвавший проблему.

Решение заключалось в том, чтобы создать поток для вызова этого раздела кода, что я сделал в другом классе с именем DataContextHelper, и реализовать блокировку в этом потоке, чтобы второй экземпляр функции не мог выполнить указанный раздел, пока блокировка не будет снята.

private void orderConsumer(ConsumeContext<Order> context)
{
    // Some code

    Thread orderThread = new Thread(new ThreadStart(() => 
                    DataContextHelper.includeOrderThread(_dataContext, message))); // Declare thread
    orderThread.Start(); // Start thread
    orderThread.Join(); // Wait for thread finish

    // Some code
}

Функция в DataContextHelper:

private static void includeOrderThread(DataContext _dataContext, Message message)
{
    lock(object)
    {
        var FoundCustomer = _dataContext.Customer
                                .Where(x => x.CustomerId == message.customerId)
                                .SingleOrDefault();

        if (FoundCustomer != null)
        {
            var Order = _dataContext.Order
                            .Where(x => x.Customer == FoundCustomer)
                            .SingleOrDefault(x => x.ExternalId == message.externalId);

            if (Order == null)
            {
                var OrderNew = new Order
                {
                    Externalid = message.externalId, 
                    Customer = FoundCustomer
                };

                _dataContext.Order.Add(OrderNew);
                _dataContext.SaveChanges();
            }
        }
    }
}

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

Чтение настроек диагностики Azure Entra ID
Конечная точка диспетчера трафика Azure: следующие местоположения, указанные в свойстве geoMapping для конечной точки, не поддерживаются
Измените конфигурацию для регистрации приложения, чем для корпоративного приложения
Новая сетевая карта с общедоступным IP-адресом не имеет подключения к Интернету, а существующая сетевая карта работает нормально
Как скопировать все контейнеры без явного упоминания в массиве между двумя разными учетными записями хранения
Предоставление доступа субъекту службы управляемой идентификации — Microsoft Graph PowerShell
Репликация базы данных SQL Azure и хранилища BLOB-объектов в конфигурации с несколькими регионами
Application Insights иногда не отображает запрос из вызова SQL
Скрипт Powershell работает в ISE, но не работает в Azure Runbook
Как создать переменную инициализации приложения логики Azure после действия анализа json