2012-08-25 16 views
19

Recibo "El contexto no se puede usar mientras se está creando el modelo". problema en mi aplicación web en una de mis páginas web. Esta página web en particular PUBLICA al servidor cada 2-3 segundos para actualizar la pantalla. De mis pruebas descubrí que si tengo 2 o más instancias de navegador abiertas en esta página, después de varios minutos recibo una excepción "El contexto no se puede usar mientras se está creando el modelo" desde el fondo del repositorio.EF: el contexto no se puede usar mientras se está creando el modelo. Excepción durante las solicitudes HTTP

Este código llama a un "servicio" para recuperar los datos necesarios. Este código se ejecuta en un atributo de autorización personalizado de la clase MVC Controller.

// Code in custom "Authorization" attribute on the controller 
int? stationId = stationCookieValue; // Read value from cookie 
RoomStationModel roomStationModel = RoomStationService.GetRoomStation(stationId); // Error occurs inside this call 

Aquí es el "RoomStationModel"

public class RoomStationModel 
{ 
    [Key] 
    public int RoomStationId { get; set; } 

    public int? RoomId { get; set; } 
    [ForeignKey("RoomId")] 
    public virtual RoomModel Room { get; set; } 
    /* Some other data properties.... */ 
} 

public class RoomModel 
{ 
    [Key] 
    public int RoomId { get; set; } 

    public virtual ICollection<RoomStationModel> Stations { get; set; } 
} 

Aquí es el código de la llamada de servicio:

public RoomStationModel GetRoomStation(int? roomStationId) 
{ 
    RoomStationModel roomStationModel = null; 
    if (roomStationId.HasValue) 
    { 
     using (IRepository<RoomStationModel> roomStationRepo = new Repository<RoomStationModel>(Context)) 
     { 
      roomStationModel = roomStationRepo.FirstOrDefault(rs => rs.RoomStationId == roomStationId.Value, false, new string[] { "Room" }); 
     } 
    } 

    return roomStationModel; 
} 

Aquí es el repositorio .... donde se produce el error

public class Repository<TObject> : IRepository<TObject> where TObject : class 
    { 
     protected MyContext Context = null; 

     public Repository(IDataContext context) 
     { 
      Context = context as MyContext; 
     } 

     protected DbSet<TObject> DbSet { get { return Context.Set<TObject>(); } } 

    public virtual TObject FirstOrDefault(Expression<Func<TObject, bool>> predicate, bool track = true, string[] children = null) 
    { 
     var objectSet = DbSet.AsQueryable(); 

     if (children != null) 
      foreach (string child in children) 
       objectSet = objectSet.Include(child); 

     if (track) 
      return objectSet.Where(predicate).FirstOrDefault<TObject>(predicate); 

     return objectSet.Where(predicate).AsNoTracking().FirstOrDefault<TObject>(predicate); 
    } 
} 

Captura de pantalla del error: Screenshot of error occurring

StackTrace:

at System.Data.Entity.Internal.LazyInternalContext.InitializeContext() 
    at System.Data.Entity.Internal.InternalContext.Initialize() 
    at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) 
    at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize() 
    at System.Data.Entity.Internal.Linq.InternalSet`1.Include(String path) 
    at System.Data.Entity.Infrastructure.DbQuery`1.Include(String path) 
    at System.Data.Entity.DbExtensions.Include[T](IQueryable`1 source, String path) 
    at Vanguard.AssetManager.Data.Repository`1.FirstOrDefault(Expression`1 predicate, Boolean track, String[] children) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Data\Repository.cs:line 100 
    at Vanguard.AssetManager.Services.Business.RoomStationService.GetRoomStation(Nullable`1 roomStationId) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Services\Business\RoomStationService.cs:line 61 
    at Vanguard.AssetManager.Web.Attributes.RoomStationAuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Web\Attributes\RoomStationAuthorizeAttribute.cs:line 52 
    at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor) 
    at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) 

EF Versión: 4.1 (Código primero)

+0

Esto no debería suceder. Su código está haciendo algo realmente malo porque normalmente el modelo se crea solo una vez cuando el contexto se usa por primera vez. ¿Estás seguro de que tu aplicación no recicla el grupo de aplicaciones después de cada solicitud? –

+0

No lo creo, ¿cómo puedo averiguar si se recicla el grupo de aplicaciones después de cada actualización? ¿Sería algo de IIS o en algún lugar del código? – contactmatt

+0

Una cosa que encontré interesante fue que este error solo ocurre cuando uso el atributo de autorización personalizado en mi controlador. Cuando elimino la autorización personalizada, el error desaparece. – contactmatt

Respuesta

0

Esto parece como una de dos cosas, una condición de carrera de algún tipo o un problema de "contexto de alcance" . Debe asegurarse de que el contexto se esté inicializando de manera segura y que el contexto no esté siendo accedido por diferentes subprocesos para evitar condiciones de carrera. Una causa difícil de detectar de este error es también el acceso al modelo mismo en la anulación de OnModelCreation.

30

Su repositorio tiene una vida corta (lo crea para cada llamada a GetRoomStation() pero su contexto real parece ser de larga duración (propiedad RoomServiceStation.Context). Esto significa que cada llamada a su aplicación web va a utilizar el mismo contexto

Este es el escenario de "EF en un nivel N" en el que intenta mantener algo con estado (el contexto) en el modelo arquitectónicamente sin estado de una aplicación web. Todas esas solicitudes se canalizan al mismo contexto en diferentes hilos y obtendrá una condición de carrera.

Un hilo podría estar iniciando la primera inicialización de su contexto en respo nse a una solicitud, y otro trata de usar el contexto. La segunda solicitud piensa que el contexto está listo para usar y obtienes esta excepción. Incluso puede obtener esto si tiene múltiples contextos tratando de "girar" al mismo tiempo que in another SO thread sugerido.

Puede hacer algunas cosas. Podría intentar el bloqueo pesimista del acceso a su contexto, pero está creando un cuello de botella innecesario. Podría intentar crear algún tipo de código "antes de que los clientes me llamen, inicialice el contexto", pero debe encontrar un buen lugar para hacerlo, quizás utilizando el método de "fuerza bruta" suggested in an MSDN thread.

Una mejor cosa para hacer es simplemente crear un nuevo contexto para cada solicitud a su servicio de fondo. Hay algunos gastos generales, sí, pero mínimos.La sobrecarga probablemente sea menos probable que mate el rendimiento que el bloqueo pesimista, y no estará sujeta a eventos de reciclado del grupo de aplicaciones que amplíen su aplicación web en una granja, etc.

Si se basa en el seguimiento de cambios u otra naturaleza con estado de un contexto, perderá este beneficio. En este caso, tendrá que idear un mecanismo diferente para rastrear y minimizar las visitas a la base de datos.

Desde un MSDN article esto se resume (el énfasis es mío):

If you serialize entities from one tier to another, the recommended pattern is to keep the context around on the mid-tier only long enough for a single service method call. Subsequent calls will spin up a new instance of the context to complete each task.

A thread on EF/WCF/N-tier may also give you some insights y blog post #5 de Jorge habla de EF en N capas (toda la serie podría ser una buena lectura). Y, por cierto, me encontré con exactamente lo mismo: muchos clientes llegan al contexto al mismo tiempo, lo que resulta en este problema.

+0

Estoy de acuerdo. Sugeriría investigar la inyección de dependencia para resolver esto. Recientemente utilicé Ninject (http://www.ninject.org/) con excelentes resultados para proyectos de MVC. – Gromer

+3

Estaba usando un ContainerControlledLifetimeManager al registrar mi contexto con Unity. Lo cambié a PerThreadLifetimeManager y resolví este error. – oldegreyg

+0

@ezycheez: en mi caso utilicé HierarchicalLifetimeManager para registrar mi contexto. Causó el mismo problema. Encontré el segundo problema con HierarchicalLifetimeManager y ContainerControlledLifetimeManager si modificaba manualmente los datos de la base de datos back-end. Entonces ese cambio no se reflejó en mi aplicación web front-end. Esto se debe a que para HierarchicalLifetimeManager y para ContainerControlledLifetimeManager, Unity devuelve la misma instancia (o duración única) del tipo u objeto registrado. En su lugar, uso PerThreadLifetimeManager para evitar los 2 problemas; son bloqueo y datos añejos. –

1

Me encontré con este error y pareció haberlo solucionado proporcionando una anulación del método Dispose() en el controlador. Parece que forzar el cierre de la conexión de la base de datos antes de intentar abrir uno nuevo subvierte este error.

protected override void Dispose(bool disposing) 
{ 
    if(disposing) 
    { 
     _fooRepository.Dispose(); 
    } 
    base.Dispose(disposing); 
} 
0

Experimenté este problema hoy. El problema fue que accidentalmente estaba usando la misma instancia de mi DbContext en las solicitudes. La primera solicitud crearía la instancia y comenzaría a construir el modelo, y la segunda solicitud entraría e intentaría recuperar datos mientras aún estaba desarrollándose.

Mi error fue tonto. Accidentalmente utilicé HttpContext.Current.Cache en lugar de HttpContext.Current.Items :)

Cuestiones relacionadas