web-dev-qa-db-fra.com

Résolution "L'instance ObjectContext a été supprimée et ne peut plus être utilisée pour les opérations nécessitant une connexion" InvalidOperationException

J'essaie de renseigner un GridView en utilisant Entity Frameworkm, mais chaque fois, l'erreur suivante apparaît:

"L'accesseur de propriété 'LoanProduct' sur l'objet 'COSIS_DAL.MemberLoan' a émis l'exception suivante: l'instance ObjectContext a été supprimée et ne peut plus être utilisée pour des opérations nécessitant une connexion."

Mon code est:

public List<MemberLoan> GetAllMembersForLoan(string keyword)
{
    using (CosisEntities db = new CosisEntities())
    {
        IQueryable<MemberLoan> query = db.MemberLoans.OrderByDescending(m => m.LoanDate);
        if (!string.IsNullOrEmpty(keyword))
        {
            keyword = keyword.ToLower();
            query = query.Where(m =>
                  m.LoanProviderCode.Contains(keyword)
                  || m.MemNo.Contains(keyword)
                  || (!string.IsNullOrEmpty(m.LoanProduct.LoanProductName) && m.LoanProduct.LoanProductName.ToLower().Contains(keyword))
                  || m.Membership.MemName.Contains(keyword)
                  || m.GeneralMasterInformation.Description.Contains(keyword)

                  );
        }
        return query.ToList();
    }
}


protected void btnSearch_Click(object sender, ImageClickEventArgs e)
{
    string keyword = txtKeyword.Text.ToLower();
    LoanController c = new LoanController();
    List<COSIS_DAL.MemberLoan> list = new List<COSIS_DAL.MemberLoan>();
    list = c.GetAllMembersForLoan(keyword);

    if (list.Count <= 0)
    {
        lblMsg.Text = "No Records Found";
        GridView1.DataSourceID = null;
        GridView1.DataSource = null;
        GridView1.DataBind();
    }
    else
    {
        lblMsg.Text = "";
        GridView1.DataSourceID = null;   
        GridView1.DataSource = list;
        GridView1.DataBind();
    }
}

L'erreur mentionne la colonne LoanProductName de la Gridview. Mentionné: J'utilise C #, ASP.net, SQL-Server 2008 comme base de données principale.

Je suis assez nouveau pour Entity Framework. Je ne peux pas comprendre pourquoi je reçois cette erreur. Quelqu'un peut-il m'aider s'il-vous-plaît?

111
barsan

Par défaut, Entity Framework utilise le chargement paresseux pour les propriétés de navigation. C'est pourquoi ces propriétés doivent être marquées comme virtuelles. EF crée une classe de proxy pour votre entité et remplace les propriétés de navigation pour permettre le chargement différé. Par exemple. si vous avez cette entité:

public class MemberLoan
{
   public string LoandProviderCode { get; set; }
   public virtual Membership Membership { get; set; }
}

Entity Framework renverra le proxy hérité de cette entité et fournira une instance DbContext à ce proxy afin de permettre le chargement tardif des membres ultérieurement:

public class MemberLoanProxy : MemberLoan
{
    private CosisEntities db;
    private int membershipId;
    private Membership membership;

    public override Membership Membership 
    { 
       get 
       {
          if (membership == null)
              membership = db.Memberships.Find(membershipId);
          return membership;
       }
       set { membership = value; }
    }
}

Donc, entity a une instance de DbContext qui a été utilisée pour charger une entité. C'est ton problème. Vous avez using un bloc autour de l'utilisation de CosisEntities. Ce qui dispose du contexte avant que les entités ne soient retournées. Lorsqu'un code tente ultérieurement d'utiliser la propriété de navigation chargée paresseux, il échoue car le contexte est disposé à ce moment-là.

Pour résoudre ce problème, vous pouvez utiliser le chargement rapide des propriétés de navigation dont vous aurez besoin ultérieurement:

IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);

Cela pré-chargera toutes les adhésions et le chargement paresseux ne sera pas utilisé. Pour plus de détails, voir Chargement des entités associées article sur MSDN.

151
Sergey Berezovskiy

La classe CosisEntities est votre DbContext. Lorsque vous créez un contexte dans un bloc using, vous définissez les limites de votre opération orientée données.

Dans votre code, vous essayez d'émettre le résultat d'une requête à partir d'une méthode, puis de terminer le contexte dans la méthode. L'opération à laquelle vous transmettez le résultat tente ensuite d'accéder aux entités afin de renseigner la vue en grille. Quelque part dans le processus de liaison à la grille, une propriété chargée paresseux est en cours d'accès et Entity Framework tente d'effectuer une recherche pour obtenir les valeurs. Il échoue car le contexte associé est déjà terminé.

Vous avez deux problèmes:

  1. Vous chargez des entités paresseuses lorsque vous vous liez à la grille. Cela signifie que vous effectuez de nombreuses opérations de requête distinctes sur SQL Server, qui vont tout ralentir. Vous pouvez résoudre ce problème en paramétrant les propriétés associées par défaut ou en demandant à Entity Framework de les inclure dans les résultats de cette requête à l'aide de l'option Include méthode d'extension.

  2. Vous terminez votre contexte prématurément: un DbContext devrait être disponible dans toute l'unité de travail exécutée, ne le supprimant que lorsque vous avez terminé le travail en cours. Dans le cas d'ASP.NET, une unité de travail est généralement la requête HTTP traitée.

27
Paul Turner

Ligne de fond

Votre code a récupéré des données (entités) via un cadre d'entité avec le chargement différé activé et, une fois le DbContext supprimé, votre code fait référence à des propriétés (entités liées/relation/navigation) qui n'étaient pas explicitement demandées.

Plus précisement

La InvalidOperationException avec ce message signifie toujours la même chose: vous demandez des données (entités) à l'entité-cadre après la suppression du DbContext.

Un cas simple:

(ces classes seront utilisées pour tous les exemples de cette réponse et supposent que toutes les propriétés de navigation ont été configurées correctement et disposent de tables associées dans la base de données)

public class Person
{
  public int Id { get; set; }
  public string name { get; set; }
  public int? PetId { get; set; }
  public Pet Pet { get; set; }
}

public class Pet 
{
  public string name { get; set; }
}

using (var db = new dbContext())
{
  var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);
}

Console.WriteLine(person.Pet.Name);

La dernière ligne jette le InvalidOperationException parce que dbContext n'a pas désactivé le chargement différé et que le code accède à la propriété Pet navigation après la suppression du contexte par l'instruction using.

Débogage

Comment trouvez-vous la source de cette exception? Les règles générales de débogage dans Visual Studio s’appliquent non seulement à l’exception elle-même, qui sera renvoyée exactement à l’emplacement où elle se produit, mais également au point suivant: placez des points d’arrêt stratégiques et inspectez vos variables , soit en survolant la souris. sur leurs noms, ouvrez une fenêtre (Quick) Watch ou utilisez les différents panneaux de débogage tels que Locals et Autos.

Si vous souhaitez savoir où la référence est définie ou non, cliquez avec le bouton droit sur son nom et sélectionnez "Rechercher toutes les références". Vous pouvez ensuite placer un point d'arrêt à chaque emplacement qui demande des données et exécuter votre programme avec le débogueur attaché. Chaque fois que le débogueur s'interrompt sur un tel point d'arrêt, vous devez déterminer si votre propriété de navigation aurait dû être renseignée ou si les données demandées sont nécessaires.

Façons d'éviter

Désactiver le chargement paresseux

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}

Avantages: Au lieu de lancer l'exception InvalidOperationException, la propriété sera nulle. Accéder aux propriétés de null ou tenter de modifier les propriétés de cette propriété générera une exception NullReferenceException .

Comment demander explicitement l'objet si nécessaire:

using (var db = new dbContext())
{
  var person = db.Persons
    .Include(p => p.Pet)
    .FirstOrDefaultAsync(p => p.id == 1);
}
Console.WriteLine(person.Pet.Name);  // No Exception Thrown

Dans l'exemple précédent, Entity Framework matérialisera l'animal en plus de la personne. Cela peut être avantageux car il s’agit d’un simple appel de la base de données. (Cependant, le nombre de résultats renvoyés et le nombre de propriétés de navigation demandées peuvent également poser d’énormes problèmes de performances. Dans ce cas, les performances ne sont pas pénalisées, car les deux instances ne sont qu’un enregistrement et une jointure).

ou

using (var db = new dbContext())
{
  var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);

  var pet = db.Pets.FirstOrDefaultAsync(p => p.id == person.PetId);
}
Console.WriteLine(person.Pet.Name);  // No Exception Thrown

Dans l'exemple précédent, Entity Framework matérialisera l'animal indépendamment de la personne en faisant un appel supplémentaire à la base de données. Par défaut, Entity Framework effectue le suivi des objets extraits de la base de données. S'il trouve les propriétés de navigation correspondantes, il remplit automatiquement ces entités. Dans ce cas, parce que PetId sur l'objet Person correspond au Pet.Id, Entity Framework affectera le Person.Pet à la valeur Pet récupérée, avant que la valeur ne soit assigné à la variable animal.

Je recommande toujours cette approche car elle oblige les programmeurs à comprendre quand et comment le code est une donnée de requête via Entity Framework. Lorsque le code lève une exception de référence null sur une propriété d'une entité, vous pouvez presque toujours être sûr de ne pas avoir explicitement demandé ces données.

16
Erik Philips

C'est une réponse très tardive mais j'ai résolu le problème en désactivant le chargement paresseux:

db.Configuration.LazyLoadingEnabled = false;
7
Ricardo Pontual

Si vous utilisez ASP.NET Core et vous demandez pourquoi vous recevez ce message dans l'une de vos méthodes de contrôleur asynchrone, veillez à renvoyer un Task plutôt que void - ASP.NET Core dispose des contextes injectés.

(Je publie cette réponse car cette question est très fréquente dans les résultats de la recherche. Elle constitue donc un problème subtil. Peut-être est-il utile aux personnes qui en font la recherche sur Google?)

1
John

La plupart des autres réponses suggèrent un chargement rapide, mais j'ai trouvé une autre solution.

Dans mon cas, j'avais un objet EF InventoryItem avec une collection d'objets InvActivity.

class InventoryItem {
...
   // EF code first declaration of a cross table relationship
   public virtual List<InvActivity> ItemsActivity { get; set; }

   public GetLatestActivity()
   {
       return ItemActivity?.OrderByDescending(x => x.DateEntered).SingleOrDefault();
   }
...
}

Et comme je tirais de la collection d'objets enfants au lieu d'une requête contextuelle (avec IQueryable), la fonction Include() n'était pas disponible pour implémenter un chargement rapide. Au lieu de cela, ma solution a donc été de créer un contexte à partir duquel j'ai utilisé GetLatestActivity() et attach() l'objet renvoyé:

using (DBContext ctx = new DBContext())
{
    var latestAct = _item.GetLatestActivity();

    // attach the Entity object back to a usable database context
    ctx.InventoryActivity.Attach(latestAct);

    // your code that would make use of the latestAct's lazy loading
    // ie   latestAct.lazyLoadedChild.name = "foo";
}

Ainsi, vous n'êtes pas coincé avec un chargement impatient.

1
Zorgarath

Dans mon cas, je transmettais tous les modèles "Utilisateurs" à la colonne et le mappage n'était pas correctement mappé. Je viens donc de passer "Utilisateurs.Nom" et cela a été corrigé.

var data = db.ApplicationTranceLogs 
             .Include(q=>q.Users)
             .Include(q => q.LookupItems) 
             .Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), ***Users = q.Users,*** ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data }) 
             .ToList();

var data = db.ApplicationTranceLogs 
             .Include(q=>q.Users).Include(q => q.LookupItems) 
             .Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), ***Users = q.Users.Name***, ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data }) 
             .ToList();
1