web-dev-qa-db-fra.com

EF - Le contexte ne peut pas être utilisé pendant la création du modèle. Exception lors des requêtes HTTP.

Je reçois "Le contexte ne peut pas être utilisé pendant la création du modèle." émettre dans mon application Web dans l’une de mes pages Web. Cette page Web en particulier POST sur le serveur toutes les 2-3 secondes pour actualiser l'écran. D'après mes tests, j'ai constaté que si plusieurs instances de navigateur sont ouvertes sur cette page, je reçois après plusieurs minutes une exception "Le contexte ne peut pas être utilisé pendant la création du modèle" du fond du référentiel.

Ce code appelle un "service" pour récupérer les données nécessaires. Ce code est exécuté dans un attribut d'autorisation personnalisé de la classe de contrôleur MVC.

// 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

Voici le "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; }
}

Voici le code de l'appel de service ci-dessus:

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;
}

Voici le référentiel .... où l'erreur se produit

    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);
    }
}

Capture d'écran de l'erreur: Screenshot of error occurring

Trace de la pile :

  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)

Version EF: 4.1 (Code d'abord)

19
contactmatt

Votre référentiel est de courte durée (vous le créez pour chaque appel à GetRoomStation() mais votre contexte réel semble être de longue durée (propriété RoomServiceStation.Context). Cela signifie que chaque appel de votre application Web utilisera le même contexte.

Il s’agit du scénario "EF dans un N-tier" dans lequel vous essayez de conserver quelque chose d’excent (le contexte) dans le modèle sans état d’architecture d’une application Web. Toutes ces demandes sont canalisées vers le même contexte sur des threads différents et vous obtenez une condition de concurrence critique.

Un thread pourrait lancer la première initialisation de votre contexte en réponse à une demande, et un autre consiste à essayer d'utiliser le contexte. La deuxième demande pense que le contexte est prêt à être utilisé et vous obtenez cette exception. Vous pouvez même l'obtenir si vous avez plusieurs contextes essayant de "tourner" en même temps que suggéré dans un autre fil SO .

Vous pouvez faire quelques choses. Vous pouvez essayer de verrouiller de façon pessimiste l'accès à votre contexte, mais vous créez un goulot d'étranglement inutile. Vous pouvez essayer de créer une sorte de code "avant que les clients ne m'appellent, initialisez le contexte", mais vous devez trouver un bon endroit pour le faire, peut-être en utilisant la méthode "brute force" suggérée dans un thread MSDN .

Une meilleure chose à faire est simplement de créer un nouveau contexte pour la demande {tous} _ auprès de votre service back-end. Il y a des frais généraux, certes, mais minimes. Les frais généraux sont probablement moins susceptibles de nuire aux performances que le verrouillage pessimiste et ne sont pas soumis aux événements de recyclage du pool d'applications qui déplacent votre application Web sur une batterie, etc.

Si vous comptez sur le suivi des modifications ou sur une autre nature précise d'un contexte, vous perdrez cet avantage. Dans ce cas, vous devrez créer un mécanisme différent pour suivre et minimiser les accès à la base de données.

Extrait d'un article MSDN ceci se résume (j'insiste beaucoup):

Si vous sérialisez des entités d'un niveau à un autre, le recommandé le modèle consiste à garder le contexte sur le niveau intermédiaire suffisamment longtemps pour un seul appel de méthode de service. Les appels suivants créeront une nouvelle instance du contexte pour terminer chaque tâche.

Un fil de discussion sur EF/WCF/N-tier peut également vous donner des idées , et le post de Jorge # 5 parle de EF dans N-Tiers (la série entière pourrait être une bonne lecture). Et, soit dit en passant, j’ai rencontré exactement le même problème: de nombreux clients ont été confrontés au même contexte en même temps, ce qui a entraîné ce problème.

30
CRAGIN

J'ai rencontré cette erreur et j'ai semblé l'avoir résolue en fournissant un remplacement à la méthode Dispose () dans le contrôleur. Il semblerait que forcer la fermeture de la connexion à la base de données avant d’essayer d’en ouvrir une nouvelle sous-invalide cette erreur.

protected override void Dispose(bool disposing)
{
   if(disposing)
   {
        _fooRepository.Dispose();
   }
   base.Dispose(disposing);
}
1
Matt Broyles

J'ai connu ce problème aujourd'hui. Le problème était que j'utilisais accidentellement la même instance de mon DbContext sur plusieurs requêtes. La première requête créerait l'instance et commencerait à construire le modèle, et la seconde requête entrerait et essaierait de récupérer des données pendant sa constitution.

Mon erreur était idiote. J'ai accidentellement utilisé HttpContext.Current.Cache au lieu de HttpContext.Current.Items :)

0
Peter Morris

Cela semble être l’une des deux choses suivantes: une situation de concurrence critique ou un problème de "contexte". Assurez-vous que le contexte est en cours d'initialisation de manière sécurisée pour les threads et qu'il n'est pas accédé par différents threads afin d'éviter les situations de concurrence. Une cause difficile à détecter de cette erreur est également l'accès au modèle lui-même dans le remplacement OnModelCreation.

0
JTMon