¿Usar Transactions o SaveChanges (falso) y AcceptAllChanges ()?

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

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

¿Qué pasa si algo sale mal? ¿No tengo que retroceder o, tan pronto como mi método se sale del scope, se finaliza la transacción?

¿Qué sucede con las columnas de identidad que fueron asignadas a la mitad de la transacción? Supongo que si alguien más agregó un registro después del mío antes de que el mío fuera malo, entonces esto significa que habrá un valor de identidad faltante.

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

Con Entity Framework la mayoría de las veces SaveChanges() es suficiente. Esto crea una transacción, o se alista en cualquier transacción ambiental, y hace todo el trabajo necesario en esa transacción.

A veces, aunque el 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 así (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, se cancela toda la transacción distribuida. Desafortunadamente, Entity Framework ya descartó los cambios en context1 , por lo que no puede reproducir o registrar efectivamente el error.

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 que la llamada a SaveChanges(false) envía los comandos necesarios a la base de datos, el contexto en sí no cambia, por lo que puede volver a hacerlo si es necesario o puede interrogar al ObjectStateManager si lo desea.

Esto significa que si la transacción arroja una excepción que puede compensar, ya sea ObjectStateManager o registrando el estado de cada contexto ObjectStateManager alguna parte.

Ver la publicación de mi blog para más.

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

use context.Database.BeginTransaction.

Desde 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 } } } 

Porque alguna base de datos puede lanzar una excepción en 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(); } } }