entity-framework - net - mvc n capas
Implementando if-not-exists-insert usando Entity Framework sin condiciones de carrera (2)
Usando LINQ-to-Entities 4.0, ¿existe un patrón o constructo correcto para implementar de forma segura "if not exists then insert"?
Por ejemplo, actualmente tengo una tabla que rastrea "favoritos del 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.
Actualmente he implementado la lógica "si no existe, luego inserte" 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 con esta implementació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 ni a tragar ciegamente excepciones?
Podría tratar de envolverlo en una transacción combinada con el patrón de try / catch ''famoso'':
using (var scope = new TransactionScope())
try
{
//...do your thing...
scope.Complete();
}
catch (UpdateException ex)
{
// here the second request ends up...
}
También puede escribir un procedimiento almacenado que utiliza algunos trucos nuevos de sql 2005+
Use su identificación única combinada (ID de usuario + ID de artículo) en una declaración de actualización, luego use la función @@ RowCount para ver si el recuento de fila> 0 si es 1 (o más), la actualización encontró una fila que coincide con su ID de usuario y Artículo ID. si es 0, entonces está todo claro para insertar.
p.ej
Update tablex set userID = @UserID, ArticleID = @ArticleID (puede tener más propiedades aquí, siempre que el lugar contenga un ID único combinado) donde userID = @UserID y ArticleID = @ArticleID
if (@@ RowCount = 0) Comience Insertar en tablex ... Fin
Lo mejor de todo es que todo se hace en una sola llamada, por lo que no tiene que comparar primero los datos y luego determinar si debe insertarlos. Y, por supuesto, detendrá las inserciones dulcificadas y no arrojará ningún error (¿con gracia?)