Подтвердить что ты не робот

Внедрение if-not-exist-insert с использованием Entity Framework без условий гонки

Используя LINQ-to-Entities 4.0, есть ли правильный шаблон или конструкция для безопасной реализации "if not exists then insert"?

Например, в настоящее время у меня есть таблица, которая отслеживает "избранное пользователя" - пользователи могут добавлять или удалять статьи из своего списка избранных.

Базовая таблица не является истинным отношением "многие ко многим", но вместо этого отслеживает некоторую дополнительную информацию, такую ​​как дата добавления фаворита.

CREATE TABLE UserFavorite
(
    FavoriteId int not null identity(1,1) primary key,
    UserId int not null,
    ArticleId int not null
);

CREATE UNIQUE INDEX IX_UserFavorite_1 ON UserFavorite (UserId, ArticleId);

Вставка двух избранных с одинаковой парой User/Article приводит к ошибке повторяющегося ключа по желанию.

В настоящее время я реализовал логику "if not exists then insert" в слое данных с помощью С#:

if (!entities.FavoriteArticles.Any(
        f => f.UserId == userId && 
        f.ArticleId == articleId))
{
    FavoriteArticle favorite = new FavoriteArticle();
    favorite.UserId = userId;
    favorite.ArticleId = articleId;
    favorite.DateAdded = DateTime.Now;

    Entities.AddToFavoriteArticles(favorite);
    Entities.SaveChanges();
}

Проблема с этой реализацией заключается в том, что она подвержена условиям гонки. Например, если пользователь дважды щелкает ссылку "добавить в избранное", на сервер могут быть отправлены два запроса. Первый запрос завершается успешно, в то время как второй запрос (тот, который видит пользователь) терпит неудачу с UpdateException, обертывающим исключение SqlException для ошибки повторяющегося ключа.

С помощью хранимых процедур T-SQL я могу использовать транзакции с подсказками блокировки, чтобы гарантировать, что состояние гонки никогда не произойдет. Есть ли чистый метод для предотвращения состояния гонки в Entity Framework без использования хранимых процедур или слепо глотающих исключений?

4b9b3361

Ответ 1

Вы можете попробовать обернуть его в транзакцию в сочетании с "известным" шаблоном try/catch:

using (var scope = new TransactionScope())
try
{
//...do your thing...
scope.Complete();
}
catch (UpdateException ex)
{
// here the second request ends up...
}

Ответ 2

Вы также можете написать хранимую процедуру, которая использует некоторые новые трюки из sql 2005 +

Используйте свой объединенный уникальный идентификатор (userID + articleID) в инструкции по обновлению, а затем используйте функцию @@RowCount, чтобы узнать, подсчитывает ли число строк > 0, если оно 1 (или больше), обновление обнаружило строку, соответствующую вашему идентификатору пользователя и ArticleID, если это 0, тогда вам все понятно, что нужно вставить.

например.

Обновить tablex set userID = @UserID, ArticleID = @ArticleID (у вас может быть больше свойств здесь, если в нем содержится объединенный уникальный идентификатор), где userID = @UserID и ArticleID = @ArticleID

if (@@RowCount = 0) Начать  Вставить в таблицу... Конец

Лучше всего, все это делается за один звонок, поэтому вам не нужно сначала сравнивать данные, а затем определять, следует ли вставлять их. И, конечно же, он остановит любые вставки и не вызовет ошибок (изящно?)