web-dev-qa-db-fra.com

Utilisation de transactions ou SaveChanges (false) et AcceptAllChanges ()?

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?

336
mark smith

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.

443
Alex James

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.

De 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
        } 
    } 
} 
108
user3885816