Implémentation de if-not-exist-insert en utilisant Entity Framework sans conditions de concurrence

entity-framework insert linq-to-entities

Question

À l'aide de LINQ-to-Entities 4.0, existe-t-il un modèle ou une construction correct pour mettre en œuvre en toute sécurité "sinon, insérer"?

Par exemple, j'ai actuellement un tableau qui suit les "favoris des utilisateurs" - les utilisateurs peuvent ajouter ou supprimer des articles de leur liste de favoris.

La table sous-jacente n'est pas une vraie relation plusieurs-à-plusieurs, mais suit plutôt des informations supplémentaires telles que la date à laquelle le favori a été ajouté.

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

L'insertion de deux favoris avec la même paire utilisateur / article entraîne une erreur de clé en double, comme vous le souhaitez.

J'ai actuellement implémenté la logique "si pas existant, alors insérer" dans la couche de données en utilisant 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();
}

Le problème avec cette implémentation est qu’elle est susceptible aux conditions de concurrence. Par exemple, si un utilisateur double-clique sur le lien "ajouter aux favoris", deux demandes peuvent être envoyées au serveur. La première demande aboutit, tandis que la deuxième (celle que voit l'utilisateur) échoue avec une exception UpdateException qui encapsule une exception SqlException pour l'erreur de clé en double.

Avec les procédures stockées T-SQL, je peux utiliser des transactions avec des indicateurs de verrouillage pour éviter toute condition de concurrence. Existe-t-il une méthode propre pour éviter la situation de concurrence critique dans Entity Framework sans recourir à des procédures stockées ni à avaler des exceptions de manière aveugle?

Réponse acceptée

Vous pouvez essayer de l'envelopper dans une transaction combinée avec le modèle "célèbre" try / catch:

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

Réponse populaire

Vous pouvez également écrire une procédure stockée qui utilise de nouvelles astuces de SQL 2005+

Utilisez votre identifiant unique combiné (userID + articleID) dans une instruction de mise à jour, puis utilisez la fonction @@ RowCount pour voir si la ligne compte> 0 si elle est égale à 1 (ou plus), la mise à jour a trouvé une ligne correspondant à votre ID utilisateur et à ArticleID si c'est 0, alors vous êtes tout à fait libre d'insérer.

par exemple

Mise à jour de la tablex set userID = @UserID, ArticleID = @ArticleID (vous pourriez avoir plus de propriétés ici, tant que le contient un ID unique combiné) où userID = @UserID et ArticleID = @ArticleID

if (@@ RowCount = 0) Commence l'insertion dans tablex ... Fin

Mieux encore, tout se fait en un seul appel. Vous n'avez donc pas à comparer les données, puis à déterminer si vous devez insérer des données. Et bien sûr, cela arrêtera toute insertion de doublons et ne renverra aucune erreur (gracieusement?)



Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow