C# одновременные вызовы для sql db
В моем C# проекте у меня есть метод, который спрашивает, существует ли объект в БД, и если нет, то создает его. Теперь, если два пользователя задают один и тот же вопрос одновременно, они оба получают нулевое значение, поэтому поток будет сохранять в db, что невозможно для двух дубликатов, поэтому возникнет исключение sql. Как я могу решить эту проблему, пожалуйста?
вот мой код:
var date = DateTime.UtcNow.Date;
var todayCelebPageView = _celebPageViewsRepo.GetAll().SingleOrDefault(d => d.iCelebId == celebId && d.dDate == date);
if (todayCelebPageView != null)
{
todayCelebPageView.iScore++;
_celebPageViewsRepo.Save();
}
else
{
todayCelebPageView = new MovliCelebPageView() {dDate = date, iCelebId = celebId, iScore = 1};
_celebPageViewsRepo.Add(todayCelebPageView);
_movliRepository.DbContext.Entry(todayCelebPageView).State = System.Data.EntityState.Added;
_celebPageViewsRepo.Save();
}
3 ответа
На самом деле нет простого ответа на этот вопрос, это общая проблема с рядом решений.
Некоторые варианты могут быть:
- Перехватите правильное исключение SQL и повторите попытку соответственно
- Создайте очередь для этих вызовов базы данных и обрабатывайте их по одному
- Некоторая реализация блокировок, либо в базе данных (возможно, путем ее оборачивания в транзакции), либо в самом коде.
Что еще нужно учитывать, так это то, что должно произойти с точки зрения бизнеса, когда предпринимаются две попытки создать запись одновременно.
Должен ли человек, который создал запись, последний раз победить? Должен ли первый человек победить, а второй получить ошибку? Или вы должны написать первую запись и обновить ее со второй?
Ответ на этот вопрос будет полностью зависеть от специфики того, что вы пытаетесь сделать в своем приложении.
Переместите логику проверки и создайте на уровень процедуры, тогда она будет обработана с изоляцией транзакции:
IF NOT EXISTS (SELECT 'non-empty' FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.TABLE_NAME') AND type in (N'U'))
CREATE TABLE dbo.TABLE_NAME
Но вы все равно должны обернуть свой метод и обработать исключение в соответствии с Number
собственностью SqlException
:
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(queryString, connection);
try
{
command.Connection.Open();
command.ExecuteNonQuery();
}
catch (SqlException ex)
{
for (int i = 0; i < ex.Errors.Count; i++)
{
errorMessages.Append("Index #" + i + "\n" +
"Message: " + ex.Errors[i].Message + "\n" +
"LineNumber: " + ex.Errors[i].LineNumber + "\n" +
"Source: " + ex.Errors[i].Source + "\n" +
"Procedure: " + ex.Errors[i].Procedure + "\n");
}
Console.WriteLine(errorMessages.ToString());
}
}
Системные сообщения об ошибках
Причина и разрешение ошибок ядра СУБД
Вы должны обернуть тест на существование и вставить в транзакцию. Таким образом, второй вызов для проверки существования будет заблокирован, пока первый завершается.