Контекст: у меня есть приложение в 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();
}
}
Но почему-то дубликаты все равно случаются, как будто его не удалось найти.
Я что-то пропустил?
Примечание: используйте SingleOrDefaultAsync
.
Вы используете HiLo? Если нет, то используйте синхронный Add
.
Являются ли дублирующиеся сообщения от клиента результатом нескольких нажатий кнопки, хотя следовало использовать один щелчок? Если это так, это может повысить эффективность предотвращения таких множественных щелчков мышью на клиенте.
@AlexanderPetrov, не будет ли забытое «ожидание» поймано компилятором? Кроме того, не могли бы вы объяснить, почему использовать SingleOrDefaultAsync, а не для добавления? Нет, я использую Microsoft.EntityFrameworkCore.
@AndrewMorton, наверное. Но я не могу это контролировать, это стороннее приложение.
Вам следует выделить входящие заказы в отдельную таблицу и считать их дополнительным заданием. Таким образом, вы также можете убедиться, что внешний идентификатор еще не был прочитан, и закрыть дубликат как таковой. Это также помогает, когда вы хотите отключить контекст заказа, чтобы служба могла читать заказы извне. Это действительно требует немного больше усилий, но по моему опыту работы в крупной компании электронной коммерции я могу сказать, что это дает вам лучший контроль.
Это нехорошее решение. Как я уже говорил в вопросе, у меня есть подобъект, который идет вместе с ним, поэтому мне понадобится еще одна таблица, чтобы связать заказ с клиентом. Было бы идеально отложить второй SingleOrDefault(), чтобы AddSync() и SaveChangesAsync() могли выполниться первыми и таким образом найти дубликат. Кроме того, второе сообщение может отличаться от первого (в очень редких случаях пользователь меняет порядок, и возникают проблемы с подключением, из-за которых отправляются два сообщения от одного и того же пользователя, но в разном порядке), поэтому мне нужно обновить первая запись, соответствующая новому заказу.
Я обнаружил, что моя проблема — это просто старая проблема с названием «состояние гонки», каждое сообщение запускало функцию, создавая экземпляр, вызвавший проблему.
Решение заключалось в том, чтобы создать поток для вызова этого раздела кода, что я сделал в другом классе с именем 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();
}
}
}
}
Убедитесь, что
await
нигде не забыт.