2010-11-15 1 views
14

En utilisant LINQ-to-Entities 4.0, existe-t-il un modèle ou une construction correcte pour implémenter en toute sécurité "if not exists then insert"?Implémentation si-non-existe-insertion à l'aide d'Entity Framework sans conditions de concurrence

Par exemple, j'ai actuellement une table qui suit les «favoris de l'utilisateur» - les utilisateurs peuvent ajouter ou supprimer des articles de leur liste de favoris.

La table sous-jacente n'est pas une véritable relation plusieurs-à-plusieurs, mais effectue plutôt le suivi d'informations supplémentaires telles que la date d'ajout du favori. L'insertion de deux favoris avec la même paire Utilisateur/Article entraîne une erreur de clé dupliquée, si nécessaire.

Je suis actuellement mis en œuvre le « if not exists puis insérez » logique 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 mise en œuvre est qu'il est sensible aux conditions course. Par exemple, si un utilisateur clique deux fois sur le lien "Ajouter aux favoris", deux demandes peuvent être envoyées au serveur. La première demande réussit, tandis que la deuxième demande (celle que l'utilisateur voit) échoue avec une exception UpdateException enveloppant 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 m'assurer qu'aucune condition de concurrence ne se produit. Existe-t-il une méthode propre pour éviter la condition de concurrence dans Entity Framework sans avoir recours à des procédures stockées ou à des avalement aveugles?

+2

avez-vous regardé 'using (var tran = new TransactionScope())'? – RPM1984

Répondre

-1

Vous pouvez essayer de l'envelopper dans une transaction combinée à la « célèbre » try/modèle de capture:

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

Je ne vois pas ce que l'utilisation d'une transaction explicite vous obtient ici. Dans la mesure où je peux travailler, le comportement sera exactement le même avec ou sans la transaction. Pourriez-vous m'expliquer? –

+0

Il est évidemment moins efficace que d'utiliser une fusion dans une procédure stockée, mais cela semble être la meilleure option dans EF, à condition que le type de transaction soit correctement défini. Nous avons récemment utilisé une solution similaire pour un projet plus complexe que ma question initiale - une commande de sauvegarde complète qui devait gérer les conflits de fusion. – ShadowChaser

+4

Je ne vois pas ce que le TransactionScope englobant ajoute non plus. S'il y a une condition de concurrence, vous obtiendrez une exception UpdateException, n'est-ce pas? – aknuds1

1

Vous pouvez également écrire une procédure stockée qui utilise quelques nouveaux trucs de sql 2005+

Utilisez votre ID unique combiné (userID + articleID) dans une instruction update, puis utilisez la fonction @@ RowCount pour voir si le nombre de lignes> 0 s'il est 1 (ou plus), la mise à jour a trouvé une ligne correspondant à vos ID utilisateur et ID article , si c'est 0, alors vous êtes tous clair pour insérer.

par exemple.

Mise à jour TABLEx mis userID = @UserID, ArticleID = @ArticleID (vous pourriez avoir plus de propriétés ici, tant que le titulaire d'où un identifiant unique combiné) où userid = @UserID et ArticleID = @ArticleID

si (@@ rowcount = 0) Commencez Insérer dans tablex ... Fin

le meilleur de tous, il est tout fait en un seul appel, de sorte que vous n'avez pas d'abord comparer les données et déterminer si vous devez insérer. Et bien sûr, il arrêtera toutes les insertions en double et ne jettera aucune erreur (gracieusement?)

+0

Cela peut toujours causer des conditions de course. Disons que vous essayez de mettre à jour et rien ne se met à jour Au moment où vous vérifiez '@@ RowCount' et effectuez réellement l'insertion, une autre rangée aurait pu être insérée. Si vous avez des contraintes uniques, cela entraînera une erreur, sinon vous aurez dupliqué des données. –

+1

@@ RowCount obtiendra seulement les lignes affectées de votre dernière déclaration. il est valide pour la dernière instruction dans votre contexte actuel, et les autres sessions utilisateur n'ont aucun impact sur cela. – abend

Questions connexes