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…

Tipos Complejos en el EDM Designer de Visual Studio 2010

Una de las mejoras que se han incluido en el diseñador de entidades de Visual Studio 2010 para Entity Framework, es el soporte para crear y modificar los Complex Types (Tipos Complejos). Los Complex Types son una de las grandes características de Entity Framework, que si bien podíamos incluirlas en la versión EF1, si utilizábamos esta característica no podíamos acceder al modelo con el diseñador de Visual Studio 2008.

Visual Studio 2010 nos permite diseñar nuestro modelo EDM incluyendo estos tipos complejos, veamos un ejemplo.

Imaginemos que tenemos una entidad Empresa con las propiedades Calle, Numero, Planta, Localidad y Provincia. Queremos encapsular estas propiedades en una única propiedad compleja llamada Direccion.

Con el nuevo diseñador de Visual Studio 2010 podemos refactorizar estas propiedades y convertirlas en el tipo complejo que queramos.

ComplexType1[1]

ComplexType2[1]

El diseñador se encarga de enlazar las nuevas propiedades a su correspondiente campo en la tabla. La propiedad Direccion.Calle se enlaza con el campo Calle en la tabla y así sucesivamente con las demás propiedades que conforman el Complex Type.

 ComplexType3[1]

Cuando actualizamos una de las propiedades de un Complex Type, Entity Framework actualiza todas las propiedades del Complex Type y no sólo la que hemos cambiado. Por ejemplo:

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

   2: {

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

   4:                    where p.Id == 1

   5:                    select p).FirstOrDefault();

   6:  

   7:     empresa1.Direccion.Calle = "Actualizacion Calle 1";

   8:     

   9:     context.SaveChanges();

  10: }

y revisamos la consulta que se ejecuta en el SaveChanges, podemos ver que se actualizan todos los campos del Complex Type.

   1: update [dbo].[EmpresaSet]

   2: set [Direccion_Calle] = @0, 

   3:     [Direccion_Numero] = @1, 

   4:     [Direccion_Planta] = @2, 

   5:     [Direccion_Localidad] = @3, 

   6:     [Direccion_Provincia] = @4

   7: where ([Id] = @5)

Por el contrario, si actualizamos cualquiera de las otras propiedades que no pertenecen al Complex Type,

   1: empresa1.Nombre = "Nombre de Empresa 1";

   2:  

   3: context.SaveChanges();

el resultado es una consulta SQL con los campos que se quieren actualizar.

   1: update [dbo].[EmpresaSet]

   2: set [Nombre] = @0

   3: where ([Id] = @1)

Importante tener en consideración el comportamiento de las consultas que se ejecutan al actualizar una entidad con un Complex Type, aunque no por esta peculiaridad debemos de dejar de modelar nuestras entidades con estos tipos complejos. Gracias a ellos ganamos en simplicidad en nuestro modelo.

Saludos a todos…

Visual Studio 2010 Setup Project Prerequisitos

Cuando creamos un proyecto de Setup con Visual Studio 2010 podemos seleccionar los componente que se van a instalar (si no lo están ya), los Prerequisitos. Una vez creado el proyecto de Setup, nos vamos a las propiedades del mismo y en la opción de Prerequisites tenemos los componentes que deben de existir en el sistema para que se pueda instalar y ejecutar nuestra aplicación.

SetupPrerequisites1[1]

En la ventana de Prerequisitos podemos elegir la versión del Framework, los Interops de Office, SQL Server Express, Runtime de C++, etc, y de donde lo debe de descargar si no lo tuviera instalado.

SetupPrerequisites2[1]

Si queremos crear un Setup para una aplicación que necesita Framework 3.5 SP1, debemos tener en cuenta que no sólo tendremos con marcar esta opción en la ventana anterior. Si desmarcamos el Framework 4 Client Profile que nos viene por defecto y marcamos el Framework 3.5 SP1 que es el que necesitamos, al generar el setup e instalar en el cliente nos vamos a encontrar con el problema de que va a instalar el Framework 4.0, aunque nosotros no se lo hayamos pedido y aunque nuestra aplicación sólo necesite el Framework 3.5.

Este problema sucede aunque seleccionemos el Framework 3.5 en la ventana de creación de proyecto.

SetupPrerequisites_2D00_NewProject[1]

El problema reside en las condiciones de ejecución del setup (botón derecho en el proyecto de setup, menú View y seleccionamos Launch Conditions).

En esta ventana, tenemos las condiciones de requerimientos para ejecutar el setup y por defecto tenemos una condición que comprueba que el Framework esté instalado.

SetupPrerequisitesLaunchCondition[1]

Si suponemos que hemos creado un Setup Project para Framework 3.5 SP1, ¿por qué en las propiedades de la condición comprueba que esté instalado el .NET Framework 4? Pues tendremos que cambiar también esta opción para que nuestro setup no compruebe ni instale el Framework 4, cuando queremos instalar una aplicación en Framework 3.5

Voy a enviar el posible bug a Microsoft a ver que dicen sobre él.

Saludos a todos…