Реализация вставки «если не существует» с использованием Entity Framework без условий гонки - PullRequest
15 голосов
/ 16 ноября 2010

Используя LINQ-to-Entities 4.0, существует ли правильный шаблон или конструкция для безопасной реализации «если не существует, то вставьте»?

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

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

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);

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

В настоящее время я реализовал логику «если не существует, то вставьте» на уровне данных с использованием C #:

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 без использования хранимых процедур или слепого проглатывания исключений?

Ответы [ 2 ]

1 голос
/ 22 апреля 2011

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

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

например

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

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

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

0 голосов
/ 19 апреля 2011

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

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