Usando Transactions o SaveChanges (false) y AcceptAllChanges ()?


He estado investigando transacciones y parece que se cuidan solas en EF siempre y cuando pase false a SaveChanges() y luego llame AcceptAllChanges() si no hay errores:

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

¿Qué pasa si algo sale mal? ¿no tengo que revertir o, tan pronto como mi método sale de alcance, se termina la transacción?

¿Qué sucede con las columnas de sangría que se asignaron a mitad de la transacción? Supongo que si alguien más añadiera un disco después del mío antes de que el mío saliera mal entonces esto significa que habrá un valor de identidad faltante.

¿Hay alguna razón para usar la clase estándar TransactionScope en mi código?

Author: Liam, 2009-05-03

3 answers

Con Entity Framework la mayor parte del tiempo SaveChanges() es suficiente. Esto crea una transacción, o se enlista en cualquier transacción ambiental, y hace todo el trabajo necesario en esa transacción.

A veces, aunque el emparejamiento SaveChanges(false) + AcceptAllChanges() es útil.

El lugar más útil para esto es en situaciones en las que desea realizar una transacción distribuida en dos contextos diferentes.

Es decir, algo como esto (malo):

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() tiene éxito pero context2.SaveChanges() falla toda la transacción distribuida se cancela. Pero desafortunadamente, Entity Framework ya ha descartado los cambios en context1, por lo que no puede volver a reproducir o registrar efectivamente el fallo.

Pero si cambias tu código para que se vea así:

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

}

Mientras la llamada a SaveChanges(false) envía los comandos necesarios a la base de datos, el contexto en sí no se cambia, por lo que puede hacerlo de nuevo si es necesario, o puede interrogar al ObjectStateManager si lo desea.

Esto significa que si la transacción realmente lanza una excepción que puede compensar, ya sea volviendo a intentar o registrando el estado de cada contexto ObjectStateManager en algún lugar.

Ver mi blog post para más.

 431
Author: Alex James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-20 06:03:54

Si está utilizando EF6 (Entity Framework 6+), esto ha cambiado para las llamadas de base de datos a SQL.
Véase: http://msdn.microsoft.com/en-us/data/dn456843.aspx

Use contexto.Base.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
        } 
    } 
} 
 101
Author: user3885816,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-03 12:40:23

Porque alguna base de datos puede lanzar una excepción a dbContextTransaction.Commit() así que mejor esto:

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(); 
    } 
  } 
} 
 -4
Author: eMeL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-06 02:19:31