Vous utilisez Transactions ou SaveChanges (false) et AcceptAllChanges ()?

346

J'ai enquêté sur des transactions et il semble qu'elles se prennent en charge dans EF aussi longtemps que je passe falseà SaveChanges()puis appelle AcceptAllChanges()s'il n'y a pas d'erreur:

SaveChanges(false);
// ...
AcceptAllChanges();

Et si quelque chose tourne mal? ne dois-je pas revenir en arrière ou, dès que ma méthode sort du champ d'application, la transaction est-elle terminée?

Qu'advient-il des colonnes d'indentation qui ont été affectées à mi-chemin de la transaction? Je suppose que si quelqu'un d'autre a ajouté un enregistrement après le mien avant que le mien ne tourne mal, cela signifie qu'il y aura une valeur d'identité manquante.

Y a-t-il une raison d'utiliser la TransactionScopeclasse standard dans mon code?

Mark Smith
la source
1
Cela m'a aidé à comprendre pourquoi SaveChanges(fase); ... AcceptAllChanges();était un modèle en premier lieu. Remarquez comment la réponse acceptée à la question ci-dessus est écrite par l'auteur d'un blog - et ce blog est référencé dans l'autre question. Tout se rassemble.
The Red Pea

Réponses:

451

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, même si l' SaveChanges(false) + AcceptAllChanges()association est utile.

L'endroit le plus utile pour cela est dans les situations où vous souhaitez effectuer une transaction distribuée dans 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, la transaction distribuée entière est abandonnée. Mais malheureusement, Entity Framework a déjà ignoré les modifications context1, vous ne pouvez donc pas rejouer ou enregistrer efficacement l'échec.

Mais si vous changez votre code pour qu'il ressemble à 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();

}

Pendant que l'appel à 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 refaire si nécessaire, ou vous pouvez interroger le ObjectStateManagersi vous le souhaitez.

Cela signifie que si la transaction lève réellement une exception, vous pouvez compenser, en réessayant ou en enregistrant l'état de chaque contexte ObjectStateManagerquelque part.

Voir mon article de blog pour en savoir plus.

Alex James
la source
3
C'est super, merci ... Donc, si quelque chose échoue, je ne dois pas revenir en arrière ?? SaveChanges, le marque pour être enregistré, mais ne se valide pas jusqu'à ce que j'accepte tous les changements .. mais si quelque chose ne va pas .. je devrai annuler pour que mon objet retourne à son état correct?
Mark Smith,
33
@Mark: si par "roll-back" vous voulez dire, ramenez vos objets à l'état dans lequel ils se trouvent dans la base de données, alors non, vous ne voudriez pas le faire car vous perdriez toutes les modifications apportées par l'utilisateur aux objets . SaveChanges(false)effectue la mise à jour réelle de la base de données, tout en AcceptAllChanges()disant à EF: "D'accord, vous pouvez oublier quelles choses doivent être enregistrées, car elles ont été enregistrées avec succès." En cas d' SaveChanges(false)échec, AcceptAllChanges()il ne sera jamais appelé et EF considérera toujours votre objet comme ayant des propriétés qui ont été modifiées et doivent être sauvegardées dans la base de données.
BlueRaja - Danny Pflughoeft
Pouvez-vous indiquer comment procéder à l'aide de Code First? Il n'y a aucun paramètre pour la méthode SaveChanges ou AcceptAllChanges
Kirsten Greed
2
J'ai posé une question sur l'utilisation de cette technique avec Code First ici
Kirsten Greed
13
Cela n'est plus possible dans EF 6.1. Savez-vous quel type d'ajustements doit être fait pour fonctionner maintenant?
Alex Dresko
113

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.

Depuis MSDN:

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
        } 
    } 
} 
user3885816
la source
52
try-catch avec roolback n'est pas nécessaire lorsque vous utilisez "using" sur la transaction.
Robert
12
Je prends une exception pour piéger l'exception comme ça. Il provoque l'échec silencieux de l'opération de base de données. En raison de la nature du SO, quelqu'un peut prendre cet exemple et l'utiliser dans une application de production.
B2K
3
@ B2K: Bon point, mais ce code est copié à partir de l' article Microsoft lié . J'espère que personne n'utilise son code en production :)
J Bryan Price
6
@Robert Selon l'article MSDN, Rollback () est nécessaire. Ils omettent délibérément une commande Rollback pour l'exemple TransactionScope. @ B2K J'ai ajouté l' throw;extrait de code MSDN et indiqué clairement qu'il ne s'agit pas de l'original de l'article MSDN.
Todd
6
(Si correct) Cela pourrait clarifier les choses: il semble que EF + MSSQL n'a pas besoin de Rollback, mais EF + d'autres fournisseurs SQL pourraient. Étant donné qu'EF est censé être indépendant de la base de données à laquelle il parle, il Rollback()est appelé au cas où il parle à MySql ou à quelque chose qui n'a pas ce comportement automatique.
mots comme Jared
-5

Parce que certaines bases de données peuvent lever une exception à dbContextTransaction.Commit () alors mieux cela:

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

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
} 
eMeL
la source
7
Je prends une exception pour piéger l'exception comme ça. Il provoque l'échec silencieux de l'opération de base de données. En raison de la nature du SO, quelqu'un peut prendre cet exemple et l'utiliser dans une application de production.
B2K
6
N'est-ce pas essentiellement la même que cette autre réponse qui a attribué l'attribution à la page MSDN qu'elle cite? La seule différence que je vois est que vous passez falsedans context.SaveChanges();, et en plus appel context.AcceptAllChanges();.
Wai Ha Lee
@ B2K, la restauration n'est pas requise - si la transaction ne fonctionne pas, rien n'est validé. Un appel explicite à Rollback peut également échouer - voir ma réponse ici stackoverflow.com/questions/41385740/…
Ken
Le retour en arrière n'est pas ce à quoi je m'oppose. L'auteur de cette réponse a mis à jour son code pour renverser l'exception, résolvant ainsi ce à quoi je m'opposais.
B2K
Désolé, j'ai commenté mon téléphone. Todd rejette l'exception, eMeL non. Il devrait y avoir quelque chose dans la capture qui informe le développeur ou l'utilisateur d'un problème provoquant une restauration. Il peut s'agir d'écrire dans un fichier journal, de renvoyer l'exception ou de renvoyer un message à l'utilisateur.
B2K