2010-11-15 21 views
14

Usando LINQ-to-Entities 4.0, ¿existe un patrón o construcción correcto para implementar de forma segura "if not exists then insert"?Implementando if-not-exists-insert usando Entity Framework sin condiciones de carrera

Por ejemplo, actualmente tengo una tabla que rastrea "favoritos de usuario": los usuarios pueden agregar o eliminar artículos de su lista de favoritos.

La tabla subyacente no es una verdadera relación muchos a muchos, sino que rastrea información adicional como la fecha en que se agregó el favorito.

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

La inserción de dos favoritos con el mismo par de Usuario/Artículo da como resultado un error de clave duplicado, según se desee.

he implementado actualmente la lógica "si no existe, entonces insertar" en la capa de datos usando 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(); 
} 

El problema de esta aplicación es que es susceptible a las condiciones de carrera. Por ejemplo, si un usuario hace doble clic en el enlace "agregar a favoritos", se podrían enviar dos solicitudes al servidor. La primera solicitud se realiza correctamente, mientras que la segunda solicitud (la que el usuario ve) falla con una excepción de actualización que envuelve una SqlException para el error de clave duplicada.

Con los procedimientos almacenados de T-SQL puedo usar transacciones con consejos de bloqueo para garantizar que nunca se produzca una condición de carrera. ¿Existe un método limpio para evitar la condición de carrera en Entity Framework sin recurrir a procedimientos almacenados o excepciones para tragar ciegamente?

+2

¿has echado un vistazo a 'using (var trantra = new TransactionScope())'? – RPM1984

Respuesta

-1

Se podría tratar de envolverlo en una operación combinada con la 'famosa' try/patrón de captura:

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

No veo que usar una transacción explícita lo traiga aquí. Por lo que puedo averiguar, el comportamiento será exactamente igual con o sin la transacción. ¿Podrías explicarme eso? –

+0

Obviamente, es menos eficiente que usar una fusión en un procedimiento almacenado, pero esta parece ser la mejor opción dentro de EF, siempre que el tipo de transacción esté configurado correctamente. Recientemente, utilizamos una solución similar para un proyecto más complejo que mi pregunta original: un comando de guardado completo que tenía que manejar conflictos de fusión. – ShadowChaser

+4

No veo qué agrega el TransactionScope adjunto tampoco. Si hay una condición de carrera, obtendrás una UpdateException independientemente, ¿no? – aknuds1

1

También puede escribir un procedimiento almacenado que utiliza algunos trucos nuevos de SQL 2005 +

Use su identificación única combinada (userID + articleID) en una declaración de actualización, luego use la función @@ RowCount para ver si el recuento de filas> 0 si es 1 (o más), la actualización encontró una fila que coincide con su ID de usuario y ArticleID , si es 0, entonces está todo claro para insertar.

p. Ej.

actualización TABLEX establece ID de usuario = @IDUsuario, ArticleID = @ArticleID (que podría tener más propiedades aquí, siempre y cuando el dónde tiene un identificador único combinado), donde = ID de usuario y @IDUsuario ArticleID = @ArticleID

si (@@ RowCount = 0) Comience Insertar en tablex ... Fin

mejor de todo, se hace todo en una sola llamada, por lo que no tiene que comparar los datos primero y luego determinar si debe insertar. Y, por supuesto, detendrá las inserciones y no arrojará ningún error (con gracia?)

+0

Esto todavía puede causar condiciones de carrera. Digamos que intenta actualizar y nada actualizaciones. Cuando marque '@@ RowCount' y realmente realice la inserción, se podría haber insertado otra fila. Si tiene restricciones únicas, esto arrojaría un error; de lo contrario, tendrá datos duplicados. –

+1

@@ RowCount obtendrá solo las filas afectadas de su última declaración. es válido para la última declaración en su contexto actual, y otras sesiones de usuario no tienen ningún impacto sobre eso. – abend

Cuestiones relacionadas