2010-11-15 9 views
14

Utilizzo di LINQ-to-Entities 4.0, esiste un modello o un costrutto corretto per l'implementazione sicura "se non esiste quindi inserire"?Implementazione di if-not-exists-insert utilizzando Entity Framework senza condizioni di gara

Ad esempio, attualmente dispongo di una tabella che tiene traccia dei "preferiti utente" - gli utenti possono aggiungere o rimuovere articoli dal proprio elenco di preferiti.

La tabella sottostante non è una vera relazione molti-a-molti, ma invece tiene traccia di alcune informazioni aggiuntive come la data in cui è stato aggiunto il preferito.

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

Inserendo due preferiti con la stessa coppia Utente/Articolo si ottiene un errore di duplicazione della chiave, come desiderato.

ho attualmente implementato la logica "se non esiste quindi inserire" nello strato di dati utilizzando 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(); 
} 

Il problema di questa implementazione è che è suscettibile a condizioni di gara. Ad esempio, se un utente fa doppio clic sul collegamento "aggiungi ai preferiti", due richieste potrebbero essere inviate al server. La prima richiesta ha esito positivo, mentre la seconda richiesta (quella che l'utente vede) non riesce con un UpdateException che racchiude una SqlException per l'errore della chiave duplicata.

Con le stored procedure T-SQL, è possibile utilizzare le transazioni con i suggerimenti di blocco per garantire che non si verifichi mai una condizione di competizione. Esiste un metodo pulito per evitare le condizioni di competizione in Entity Framework senza ricorrere a stored procedure o ad escludere ciecamente le eccezioni?

+2

hai dato un'occhiata a 'using (var tran = new TransactionScope())'? – RPM1984

risposta

-1

Si potrebbe provare ad avvolgerla in una transazione combinato con il 'famoso' try/modello fermo:

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

Non vedo cosa usi una transazione esplicita per farti arrivare qui. Per quanto posso capire, il comportamento sarà esattamente lo stesso con o senza la transazione. Potresti spiegarmelo? –

+0

Ovviamente è meno efficiente dell'utilizzo di un'unione in una stored procedure, ma questa sembra essere l'opzione migliore in EF, a condizione che il tipo di transazione sia impostato correttamente. Di recente, abbiamo utilizzato una soluzione simile per un progetto più complesso rispetto alla mia domanda originale: un comando di salvataggio completo che doveva gestire i conflitti di unione. – ShadowChaser

+4

Non vedo nemmeno quello che aggiunge TransactionScope. Se c'è una condizione di competizione, riceverai una UpdateException a prescindere, vero? – aknuds1

1

è anche possibile scrivere una stored procedure che utilizza alcuni nuovi trucchi da SQL 2005+

Utilizzare l'ID univoco combinato (userID + articleID) in un'istruzione di aggiornamento, quindi utilizzare la funzione @@ RowCount per verificare se il numero di righe> 0 se è 1 (o più), l'aggiornamento ha rilevato una riga corrispondente al proprio ID utente e ArticoloID , se è 0, allora è tutto chiaro da inserire.

ad es.

Aggiornamento Tablex impostato userID = @UserID, ArticleID = @ArticleID (si potrebbe avere più proprietà qui, fino a quando il quale detiene un ID univoco combinato) dove userID = @UserID e ArticleID = @ArticleID

se (@@ RowCount = 0) Iniziare Inserisci nella tablex ... Fine

meglio di tutti, è tutto fatto in una sola chiamata, quindi non c'è bisogno di confrontare i dati prima e quindi determinare se è il caso inserire. E, naturalmente, fermerà tutti gli inserimenti dattili e non genererà alcun errore (con garbo?)

+0

Ciò può ancora causare condizioni di gara. Supponiamo che tu provi ad aggiornare e nulla aggiorni. Nel momento in cui si seleziona '@@ RowCount' e si esegue effettivamente l'inserimento, è possibile che sia stata inserita un'altra riga. Se si dispone di vincoli univoci, si genera un errore, altrimenti si hanno dati duplicati. –

+1

@@ RowCount riceverà solo le righe interessate dall'ultima istruzione. è valido per l'ultima istruzione nel tuo contesto attuale e altre sessioni utente non hanno alcun impatto su questo. – abend

Problemi correlati