12

Utilizando Entity Framework (primero el código en mi caso), tengo una operación que requiere que llame a SaveChanges para actualizar un objeto en el DB, y luego SaveChanges nuevamente para actualizar otro objeto. (Necesito los primeros SaveChanges para resolver un problema donde EF no puede determinar qué objeto actualizar primero).EF: ¿Cómo puedo llamar a SaveChanges dos veces dentro de una transacción?

He intentado hacer:

using (var transaction = new TransactionScope()) 
{ 
    // Do something 

    db.SaveChanges(); 

    // Do something else 

    db.SaveChanges(); 

    tramsaction.Complete(); 
} 

Cuando corro que, consigo una excepción en el segundo SaveChanges llamada, diciendo que "el proveedor subyacente falló al abrir". La excepción interna dice que MSDTC no está habilitado en mi máquina.

Ahora, he visto publicaciones en otros lugares que describen cómo habilitar MSDTC, pero parece que también necesitaría habilitar el acceso a la red, etc. Esto parece una exageración completa aquí, ya que no hay otras bases de datos involucradas, deje solo otros servidores. No quiero hacer algo que haga que toda mi aplicación sea menos segura (o más lenta).

Seguramente debe haber una manera más ligera de hacerlo (idealmente sin MSDTC)?

+0

¿Está utilizando SQL 2008? Dependiendo de cuál sea su lógica real, puede abrir múltiples conexiones sin escalar. Aquí hay una gran publicación desglosada cuando se llama al DTC: [Ámbito de la transacción Escalada automática a MSDTC] (http: // stackoverflow.com/questions/1690892/transactionscope-automatically-escalada-a-msdtc-en-algunas-máquinas) –

+0

Si desea hacer todo en una sola transacción, ¿cuál es la diferencia entre guardar todo una vez o guardar todo varias veces? En ambos casos, se guardará todo o no se guardará nada, por lo que no veo ninguna ventaja de guardar varias veces. Según el comentario siguiente: en Sql Server 2005, la apertura de múltiples conexiones dentro de una transacción (incluso si la fuente es la misma) hace que la transacción sea promovida como una transacción distribuida. Esto se mejoró en Sql Server 2008, donde puede abrir múltiples conexiones a la misma fuente de datos dentro de un trx sin causar una promoción – Pawel

+0

@markoreta: estoy usando SQL server 2012. No tengo (o quiero) conexiones múltiples, y no lo hago ¡Realmente quiero usar MSDTC! –

Respuesta

6

Es probable que sea causada por dos conexiones diferentes utilizadas en su transacción. Trate de controlar la conexión para su operación manual:

var objectContext = ((IObjectContextAdapter)db).ObjectContext; 

try { 
    object.Context.Connection.Open(); 
    using (var transaction = new TransactionScope()) { 
     // Do something 

     db.SaveChanges(); 

     // Do something else 

     db.SaveChanges(); 

     transaction.Complete(); 
    } 
} finally { 
    objectContext.Connection.Close(); 
} 
+0

Si está en el mismo DbContext, EF6.0 tiene context.Database.BeginTransaction(). Pero esto funciona bien si las operaciones en diferentes DBContexts. –

7

Llamando SaveChanges() como usted está causando que los datos se conservan en la base de datos y la EF para olvidarse de los cambios que acaba de hacer.

El truco es usar SaveChanges (falso) para que los cambios se mantengan en la base de datos pero EF no se olvide de los cambios que hace, lo que hace posible el registro/reintentos.

 var scope = new TransactionScope(
      TransactionScopeOption.RequiresNew, 
      new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable } 
     ); 

     using (scope) 
     { 
      Entities context1 = new Entities(); 
      // Do Stuff 
      context1.SaveChanges(false); 

      Entities context2 = new Entities(); 
      // Do Stuff 
      context2.SaveChanges(false); 

      scope.Complete(); 
      context1.AcceptAllChanges(); 
      context2.AcceptAllChanges(); 
     } 

P.S. Tan pronto como tenga más de una conexión abierta dentro de un workscope, escalará a DTC.

+0

Gracias. Eso suena eminentemente plausible, aunque en mi caso no me importa el estado del contexto local si hay un error, ya que en ese caso voy a bombardear y destruir el contexto de todos modos. Aún así, es bueno saberlo. –

+0

¿Qué sucede si la primera operación de actualización funciona bien pero la segunda operación de actualización falla, son consistentes sus datos en la base de datos? –

+0

Dado que todo está envuelto en TransactionScope, entonces ¡espero que sí! –

11

Sé que es un tipo de respuesta tardía, pero me pareció útil compartir.

Ahora en EF6 que es más fácil acheeve esto mediante el uso dbContext.Database.BeginTransaction()

así:

using (var context = new BloggingContext()) 
{ 
    using (var dbContextTransaction = context.Database.BeginTransaction()) 
    { 
     try 
     { 
      // do your changes 
      context.SaveChanges(); 

      // do another changes 
      context.SaveChanges(); 

      dbContextTransaction.Commit(); 
     } 
     catch (Exception) 
     { 
      dbContextTransaction.Rollback(); 
     } 
    } 
} 

para más información vistazo a this

vez más es en EF6 Adelante

+0

¡Gracias, eso es más como eso! –

+0

De nada :) –

+0

Debería hacer más con el bloque catch que simplemente retrotraer y tragar la excepción. Log, rethrow, ... –

1

Para la final respuesta seleccionada arriba, hay un error tipográfico. línea corregida a continuación:

objectContext.Connection.Open(); 

Además, necesitará agregar referencias para System.Data.Entity.Infrastructure y System.Transactions

Cuestiones relacionadas