Transacción no permitida en Entity Framework

Actualizado. El problema no era la transacción del contexto de Entity Framework (Entity Framework no hace uso de transacciones para la consulta de datos si no se lo especificamos, aunque si para las actualizaciones (insert, update o delete)). El problema reside en que recorriendo la colección hay un Reader abierto y no permite la modificación de los datos del mismo. Gracias a Unai por las aclaraciones con las transacciones y el Reader en Entity Framework.

El problema nos surge cuando obtenemos una colección de entidades, las recorremos con un ForEach y realizamos alguna actualización de estas entidades o de otras dentro de este ForEach, realizando un SaveChanges para cada una de las entidades de la colección que estamos recorriendo.

   1: using (var context = new ClientesModelContainer())

   2: {

   3:     var empresaList = from p in context.EmpresaSet

   4:                       select p;

   5:  

   6:     foreach (var empresa in empresaList)

   7:     {

   8:         empresa.Direccion.Calle = "Actualizacion Calle 1";

   9:         empresa.Nombre = "Nombre Empresa 1";

  10:  

  11:         Vehiculo vehiculo = new Vehiculo();

  12:         vehiculo.Marca = "Marca 1";

  13:         vehiculo.Modelo = "Modelo 1";

  14:  

  15:         context.AddToVehiculoSet(vehiculo);

  16:  

  17:         context.SaveChanges();

  18:         Console.WriteLine("Actualizada {0}", empresa.Nombre);

  19:     }

  20:  

  21:     Console.ReadLine();

  22: }

El resultado de esta actualización es un EntityException que nos indica que se ha producido un error iniciando una transacción en el proveedor de conexión y con el siguiente mensaje de exepción ‘New transaction is not allowed because there are other threads running in the session’ (No están permitidas nuevas transacciones cuando existen otros hilos ejecutándose en la sesión).

Esto nos indica que cuando Entity Framework ejecuta el SaveChanges e intenta ejecutar actualizaciones sobre la conexión que está abierta por el ForEach, el proveedor SqlClient no se lo permite porque existe un Reader abierto y no es posible la actualización del mismo.

Veamos las posibles soluciones a este problemilla.

1. Ejecutar el SaveChanges fuera del ForEach y al final de todas las modificaciones. Esta solución nos pueda valer, siempre que nuestro código no necesite que se actualice la base de datos y se pueda esperar al final para realizar los cambios, el código quedaría tal como sigue.

   1: using (var context = new ClientesModelContainer())

   2: {

   3:    var empresaList = from p in context.EmpresaSet

   4:                      select p;

   5:  

   6:    foreach (var empresa in empresaList)

   7:    {

   8:        empresa.Direccion.Calle = "Actualizacion Calle 1";

   9:        empresa.Nombre = "Nombre Empresa 1";

  10:  

  11:        Vehiculo vehiculo = new Vehiculo();

  12:        vehiculo.Marca = "Marca 1";

  13:        vehiculo.Modelo = "Modelo 1";

  14:        context.AddToVehiculoSet(vehiculo);

  15:  

  16:        Console.WriteLine("Actualizada {0}", empresa.Nombre);

  17:    }

  18:  

  19:    context.SaveChanges();

  20:  

  21:    Console.ReadLine();

2. Obtener la colección antes de recorrerla. Este es el método que prefiero, ya que obtenemos la colección de entidades en un Array o List para que no se mantenga abierto el Reader.

   1: using (var context = new ClientesModelContainer())

   2: {

   3:     var empresaList = (from p in context.EmpresaSet

   4:                       select p).ToList();

   5:  

   6:     foreach (var empresa in empresaList)

   7:     {

   8:         empresa.Direccion.Calle = "Actualizacion Calle 1";

   9:         empresa.Nombre = "Nombre Empresa 1";

  10:  

  11:         Vehiculo vehiculo = new Vehiculo();

  12:         vehiculo.Marca = "Marca 1";

  13:         vehiculo.Modelo = "Modelo 1";

  14:  

  15:         context.AddToVehiculoSet(vehiculo);

  16:  

  17:         context.SaveChanges();

  18:         Console.WriteLine("Actualizada {0}", empresa.Nombre);

  19:     }

  20:  

  21:     Console.ReadLine();

  22: }

3. Especificar la Transacción. Podemos especificar, haciendo uso del TransactionScope, la transacción de la conexión para delegar en este la actualización de los datos.

   1: using (TransactionScope transactionScope = new TransactionScope())

   2: {

   3:     using (var context = new ClientesModelContainer())

   4:     {

   5:         var empresaList = from p in context.EmpresaSet

   6:                           select p;

   7:  

   8:         foreach (var empresa in empresaList)

   9:         {

  10:             empresa.Direccion.Calle = "Actualizacion Calle 1";

  11:             empresa.Nombre = "Nombre Empresa 1";

  12:  

  13:             Vehiculo vehiculo = new Vehiculo();

  14:             vehiculo.Marca = "Marca 1";

  15:             vehiculo.Modelo = "Modelo 1";

  16:  

  17:             context.AddToVehiculoSet(vehiculo);

  18:  

  19:             context.SaveChanges();

  20:             Console.WriteLine("Actualizada {0}", empresa.Nombre);

  21:         }

  22:  

  23:         Console.ReadLine();

  24:     }

  25: }

 

Este código no suele ser el más empleado, ya que las aplicaciones que desarrollamos con Entity Framework se preparan para que soporten la desconexión de los contextos y de las entidades (n-tiers), pero puede convertirse en un dolor de cabeza, si recibimos este tipo de error que no es muy descriptivo.

 

Saludos a todos…

8 comentarios sobre “Transacción no permitida en Entity Framework”

  1. Hi Alberto,

    There are wa few things that confuse me about your post (and the correction at the start).

    By default, EF uses a dbTransaction with every database interaction – reading and writing. Additionally, by default your SQL Server (you did say you are using SqlClient) connection string (if constructed by the EDM Wizard) will include MultipleActiveResultSets=True. This allows you to do multiple things on a single data reader and therefore you are able to interleave a read & a write together.

    So your statement «Entity Framework does not use transactions data to the query if not specified» is inaccurate becuse if you do not specifiy a transcation, it will use a dbTransaction. If you specify a System.Transactions.TransactionScope, then this will override the default transaction.

    And the 2nd statement («The problem is that touring the collection includes an open Reader do not allow the modification of the data on it.»), if you have MARS=true, the open reader *should* allow modifications interleaved wtih reading.

    A caveat – I am basing this on Google’s trasnlation of your blog post Hope that I did not misundertand anything.

    julie

  2. Hi Julie,

    EF don’t use any dbTransaction when run a select query to database.

    If you make a trace with SQL Server Profiler, you will see that we have a SQL:BatchStarting and SQL:BatchCompleted for entities collection reader and when we call SaveChanges we have a BEGIN TRANSACTION. In this moment I have the EntityException because I have an open reader in EF context but not a DataReader.

    EF need MARS on connection string for make multiple read to database, but when we have a collection ForEach I think EF need to track each entity on collection and EF don’t allow to make changes on it.

    If you test this code without EF, using DataReader and enabling MARS, you will see that it’s all work.

  3. al crear el transactionscope estarias encapsulando la variable del contexto para que sea el objeto de transactionscope el que administre la transaccion?

    en el segundo ejemplo al utilizar ToList() estarias consultando inmediatamente la bd hasta ahi . .entiendo . .esto supongo con el fin de que en el foreach consultes cada vez que recorres sin embargo cuando recorres con el foreach y adjuntas al context el objeto modificado no deberias hacer el savechanges fuera del foreach con eso si fallas en alguna de los registros no actualizarias la bd o para eso utilizas el scope?

  4. Hola Juancho,

    si es así, el TransactionScope se aplica al contexto de LINQ.

    Si, al hacer el ToList() se ejecuta la consulta y se recorreo para devolver una IList. El scope es para controlar la transaccionalidad de los objetos, si alguna de las actualizaciones falla se deshacen todas.

  5. Hola tengo una duda, tengo dos modelos EF de diferentes base de datos y consulto una entidad del context1 y la informacion consultada la inserto a una entidad del contexto2 al hacer un contexto2.SaveChanges() fuera o dentro del foreach, hice el ejemplo de las 2 formas (todo el negocio dentro del TransactionScope) me sale el siguiente error:

    Error del proveedor subyacente en Open

    {«Multiple simultaneous connections or connections with different connection strings inside the same transaction are not currently supported.»}

    Nose si a alguien le paso algo parecido y lo soluciono agradeceria mucho su ayuda. dejo mi email jacb@hotmail.it

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *