J'ai enquêté sur des transactions et il semble qu'elles se prennent en charge dans EF tant que je passe false
à SaveChanges()
, puis que j'appelle AcceptAllChanges()
s'il n'y a pas d'erreur:
SaveChanges(false);
// ...
AcceptAllChanges();
Et si quelque chose va mal? Ne dois-je pas revenir en arrière ou, dès que ma méthode sort du cadre, la transaction est-elle terminée?
Qu'advient-il des colonnes d'identification attribuées au milieu de la transaction? Je suppose que si quelqu'un d'autre ajoutait un disque après le mien avant que le mien ne se détériore, cela signifierait qu'il manquera une valeur d'identité.
Existe-t-il une raison d'utiliser la classe standard TransactionScope
dans mon code?
Avec Entity Framework, la plupart du temps, SaveChanges()
est suffisant. Cela crée une transaction ou s’inscrit dans une transaction ambiante et effectue tout le travail nécessaire dans cette transaction.
Parfois, le jumelage SaveChanges(false) + AcceptAllChanges()
est utile.
L'endroit le plus utile pour cela est dans les situations où vous souhaitez effectuer une transaction distribuée sur deux contextes différents.
C'est à dire. quelque chose comme ça (mauvais):
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save and discard changes
context1.SaveChanges();
//Save and discard changes
context2.SaveChanges();
//if we get here things are looking good.
scope.Complete();
}
Si context1.SaveChanges()
réussit mais context2.SaveChanges()
échoue, toute la transaction distribuée est abandonnée. Mais malheureusement, Entity Framework a déjà ignoré les modifications apportées à context1
, vous ne pouvez donc pas rejouer ni enregistrer correctement l'échec.
Mais si vous changez votre code pour ressembler à ceci:
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save Changes but don't discard yet
context1.SaveChanges(false);
//Save Changes but don't discard yet
context2.SaveChanges(false);
//if we get here things are looking good.
scope.Complete();
context1.AcceptAllChanges();
context2.AcceptAllChanges();
}
Alors que l'appel de SaveChanges(false)
envoie les commandes nécessaires à la base de données, le contexte lui-même n'est pas modifié. Vous pouvez donc le répéter si nécessaire ou interroger la ObjectStateManager
si vous le souhaitez.
Cela signifie que si la transaction lève une exception, vous pouvez compenser, soit en essayant à nouveau, soit en enregistrant l'état de chaque contexte ObjectStateManager
quelque part.
Voir monarticle de blog pour plus d'informations.
Si vous utilisez EF6 (Entity Framework 6+), cela a changé pour les appels de base de données à SQL.
Voir: http://msdn.Microsoft.com/en-us/data/dn456843.aspx
utilisez context.Database.BeginTransaction.
using (var context = new BloggingContext()) { using (var dbContextTransaction = context.Database.BeginTransaction()) { try { context.Database.ExecuteSqlCommand( @"UPDATE Blogs SET Rating = 5" + " WHERE Name LIKE '%Entity Framework%'" ); var query = context.Posts.Where(p => p.Blog.Rating >= 5); foreach (var post in query) { post.Title += "[Cool Blog]"; } context.SaveChanges(); dbContextTransaction.Commit(); } catch (Exception) { dbContextTransaction.Rollback(); //Required according to MSDN article throw; //Not in MSDN article, but recommended so the exception still bubbles up } } }