2010-07-16 32 views
28

Siendo nuevo en Entity Framework, estoy bastante atascado en cómo proceder con este conjunto de problemas. En el proyecto en el que estoy trabajando actualmente, todo el sitio está muy integrado con el modelo EF. Al principio, el acceso al contexto EF se controlaba con un bootstrapper de Dependency Injection. Por razones operacionales, no pudimos usar una biblioteca DI. Eliminé esto y usé un modelo de instancias individuales del objeto de contexto donde fue requerido. Empecé a recibir la siguiente excepción:.NET Entity Framework y transacciones

El tipo 'XXX' se ha mapeado más de una vez.

Llegamos a la conclusión de que las diferentes instancias del contexto estaban causando este problema. Luego resumí el objeto de contexto en una única instancia estática a la que accedía cada hilo/página. Ahora obtengo una de varias excepciones sobre transacciones:

No se permite la nueva transacción porque hay otros hilos que ejecutan en la sesión.

La operación de transacción no se puede realizar porque hay solicitudes pendientes trabajando en esta transacción.

ExecuteReader requiere el comando de tener una transacción cuando la conexión asignado al comando está en una transacción local pendiente. La propiedad de transacción del comando no se ha inicializado.

La última de estas excepciones se produjo en una operación de carga. No estaba tratando de guardar el estado del contexto en el Db en el hilo que falló. Sin embargo, había otro hilo que realizaba una operación de este tipo.

Estas excepciones son intermitentes en el mejor de los casos, pero he logrado que el sitio entre en un estado en el que se rechazaron conexiones nuevas debido a un bloqueo de transacción. Desafortunadamente no puedo encontrar los detalles de la excepción.

Supongo que mi primera pregunta es, ¿debería usarse el modelo EF desde una única instancia estática? Además, ¿es posible eliminar la necesidad de transacciones en EF? He intentado usar un objeto TransactionScope sin éxito ...

Para ser honesto, estoy un montón pegado aquí, y no puedo entender por qué (lo que debería ser) las operaciones bastante simples están causando un problema tal ...

+0

Relacionados: http://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why – Steven

+0

Es una lástima que no se pueda usar un bootstrapper de IOC, porque la solución con [Ninject] (http : //www.ninject.org/) sería vincular una instancia "común" al _cance scope_, como otros han sugerido: 'kernel.Bind >(). Para > () .InRequestScope(); '- siendo la parte importante **' InRequestScope' ** – drzaus

Respuesta

50

Crear un ObjectContext global en una aplicación web es muy malo. La clase ObjectContext no es segura para subprocesos. Se basa en el concepto de unit of work y esto significa que se utiliza para operar un solo caso de uso: por lo tanto, para una transacción comercial. Está destinado a manejar una única solicitud.

La excepción que obtienes sucede porque para cada solicitud creas una nueva transacción, pero intenta utilizar esa misma ObjectContext. Tienes suerte de que el ObjectContext detecta esto y arroja una excepción, porque ahora descubres que esto no funcionará.

Por favor, piensa en esto por qué esto no puede funcionar. El ObjectContext contiene un caché local de entidades en su base de datos. Le permite hacer un montón de cambios y finalmente enviar esos cambios a la base de datos. Cuando se usa un único estático ObjectContext, con múltiples usuarios llamando al SaveChanges en ese objeto, ¿cómo se supone que se debe saber exactamente qué se debe comprometer y qué no?Debido a que no lo sabe, guardará todos los cambios, pero en ese momento otro usuario podría estar haciendo cambios. Cuando tengas suerte, EF o tu base de datos fallarán, porque las entidades están en un estado inválido. Si tienes objetos desafortunados que están en un estado inválido, se guardan con éxito en la base de datos y es posible que descubras semanas después que tu base de datos está llena de basura. La solución a su problema es create at least one ObjectContext per request. Aunque en teoría podría almacenar en caché un contexto de objeto en la sesión del usuario, esta también es una mala idea, ya que el ObjectContext generalmente vivirá demasiado tiempo y contendrá datos obsoletos (debido a que su caché interna no se actualizará automáticamente).

ACTUALIZACIÓN:

También tenga en cuenta que el tener una ObjectContext por hilo es tan malo como tener una sola instancia de la aplicación web completa. ASP.NET utiliza un grupo de subprocesos, lo que significa que se creará una cantidad limitada de subprocesos durante la vida de una aplicación web. Esto básicamente significa que esas instancias ObjectContext en ese caso todavía vivirán durante la vida útil de la aplicación, causando los mismos problemas con la caducidad de los datos.

Puede pensar que tener un DbContext por subproceso es realmente seguro para subprocesos, pero esto no suele ser el caso, ya que ASP.NET tiene un modelo asincrónico que permite finalizar solicitudes en un subproceso diferente de donde se inició (y las últimas versiones de MVC y Web API incluso permiten que un número arbitrario de subprocesos maneje una sola solicitud en orden secuencial). Esto significa que el hilo que inició una solicitud y creó el ObjectContext puede estar disponible para procesar otra solicitud mucho antes de que finalice la solicitud inicial. Sin embargo, los objetos utilizados en esa solicitud (como una página web, un controlador o cualquier clase de negocios) podrían hacer referencia a ese ObjectContext. Dado que la nueva solicitud web se ejecuta en el mismo subproceso, obtendrá la misma instancia ObjectContext que la que usa la solicitud anterior. De nuevo, esto provoca condiciones de carrera en la aplicación y causa los mismos problemas de seguridad de subprocesos que una instancia global de ObjectContext.

+1

Bueno, de hecho eso es lo que pensé que estaba haciendo ... Normalmente nunca utilizaría objetos de contexto de datos estáticos, pero me estaba desesperando. Resulta que tuve un error de A N Otro donde tuve instancias únicas del contexto.Sin embargo, estaban dentro de un objeto estático, que parece haber sido la causa. 2 horas de mi vida no vuelven ... Gracias – sicknote

+19

¿Solo dos horas? Entonces eres un hombre afortunado :-) – Steven

+1

¿Y cuál es la mejor opción para enfrentar este tipo de problemas? – Romias

4

Como usted se refiere al "sitio" en su pregunta, supongo que esta es una aplicación web. Los miembros estáticos existen solo una vez para toda la aplicación, si está utilizando un patrón de tipo singleton con una sola instancia de contexto en toda la aplicación, todo tipo de solicitudes van a estar en todo tipo de estados en toda la aplicación.

Una única instancia de contexto estático no funcionará, pero las instancias de contexto múltiples por subproceso serán problemáticas y no se podrán mezclar y combinar contextos. Lo que necesitas es un único contexto por hilo. Hemos hecho esto en nuestra aplicación usando un patrón de tipo de inyección de dependencia. Nuestras clases de BLL y DAL toman un contexto como parámetro en los métodos, de esa manera se puede hacer algo, como a continuación:

using (TransactionScope ts = new TransactionScope()) 
{ 
    using (ObjectContext oContext = new ObjectContext("MyConnection")) 
    { 
     oBLLClass.Update(oEntity, oContext); 
    } 
} 

Si necesita llamar a otros métodos BLL/DAL dentro de su actualización (o lo que sea el método elegido) simplemente pasas el mismo contexto alrededor. De esa forma las actualizaciones/inserciones/eliminaciones son atómicas, todo dentro de un único método está usando la misma instancia de contexto, pero esa instancia no está siendo utilizada por otros hilos.