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

123

J'essaie de remplir un GridViewEntity Frameworkm en utilisant mais chaque fois que j'obtiens l'erreur suivante:

«L'accesseur de propriété 'LoanProduct' sur l'objet 'COSIS_DAL.MemberLoan' a levé l'exception suivante: l'instance ObjectContext a été supprimée et ne peut plus être utilisée pour les opérations qui nécessitent 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 LoanProductNamecolonne du Gridview. Mentionné: J'utilise C #, ASP.net, SQL-Server 2008 comme DB back-end.

Je suis assez nouveau dans Entity Framework. Je ne comprends pas pourquoi j'obtiens cette erreur. Quelqu'un peut-il m'aider s'il-vous-plaît?

barsan
la source
1
Accédez-vous à des propriétés de navigation dans la vue en grille. Si vous le faites, vous devez également inclure ces tables de navigation dans la requête. J'aimequery.Include("SomeOtherTable")
Nilesh
Essayez de créer une classe proxy pour héberger votre entité ou au moins de renvoyer un objet anonyme. De mon point de vue, utiliser ef nécessite de créer des classes proxy pour implémenter vos logiques, utilisez l'edmx comme la couche d'accès à la base de données et non comme une entreprise.
Gonzix
oui dans le gridview, je reçois également une autre colonne de table. Qui est LoanProviderName.
barsan
1
Essayez de db.MemberLoans.Include("LoanProduct").OrderByDescending()vérifier la syntaxe car je n'ai pas de VS devant moi.
Nilesh
3
Il vous suffit de continuer à inclure toutes les propriétés de navigation auxquelles vous accédez en dehors du contexte, comme db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Vérifiez les réponses de @Tragedian et @lazyberezovsky
Nilesh

Réponses:

175

Par défaut, Entity Framework utilise le chargement différé 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 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 retournera le proxy hérité de cette entité et fournira l'instance DbContext à ce proxy afin de permettre le chargement différé de l'appartenance plus tard:

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

Ainsi, l'entité a une instance de DbContext qui a été utilisée pour charger l'entité. C'est ton problème. Vous avez un usingblocage autour de l'utilisation de CosisEntities. Qui supprime le contexte avant que les entités ne soient renvoyées. Lorsqu'un code essaie ultérieurement d'utiliser la propriété de navigation chargée différé, il échoue, car le contexte est supprimé à ce moment.

Pour résoudre ce problème, vous pouvez utiliser le chargement hâtif des propriétés de navigation dont vous aurez besoin plus tard:

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

Cela préchargera toutes les adhésions et le chargement différé ne sera pas utilisé. Pour plus d'informations, consultez l' article Chargement des entités associées sur MSDN.

Sergey Berezovskiy
la source
Merci beaucoup pour vos explications et réponses utiles. En fait, j'inclus ici trois tables, donc je ne sais pas comment je peux ajouter les trois tables avec INCLUDE. pouvez-vous s'il vous plaît m'aider à ce sujet s'il vous plaît.
barsan
8
@barsan inclut simplement toutes les propriétés de navigation une par une. Par exemple, db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);cela générera une requête JOIN et retournera toutes les données à la fois.
Sergey Berezovskiy
1
Merci beaucoup lazyberezovsky. Je vous suis très reconnaissant. Vous m'avez sauvé presque un jour. De votre explication, j'en apprends plus sur Entity Framework. Merci mon ami.
barsan
Merci mon pote, parfait. J'avais une instruction d'utilisation qui limitait le chargement paresseux. Très bonne réponse.
ncbl
4
Que faire si je ne souhaite pas du tout inclure ces entités associées dans ma requête?
Ortund
32

La CosisEntitiesclasse est à vous DbContext. Lorsque vous créez un contexte dans un usingbloc, 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 essaie alors d'accéder aux entités afin de remplir la vue de grille. Quelque part dans le processus de liaison à la grille, une propriété à chargement différé 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 êtes des entités à chargement paresseux lorsque vous vous connectez à la grille. Cela signifie que vous effectuez de nombreuses opérations de requête distinctes sur SQL Server, ce qui va tout ralentir. Vous pouvez résoudre ce problème en rendant les propriétés associées chargées par défaut ou en demandant à Entity Framework de les inclure dans les résultats de cette requête à l'aide de la Includeméthode d'extension.

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

Paul Turner
la source
Merci beaucoup pour les informations utiles et la belle explication du problème. En fait, je suis tellement nouveau dans Entity Framework ainsi que dans Linq, donc cette information est vraiment une excellente leçon à apprendre.
barsan
20

Conclusion

Votre code a récupéré des données (entités) via une structure 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'ont pas été explicitement demandées.

Plus précisement

Le InvalidOperationExceptionavec ce message signifie toujours la même chose: vous demandez des données (entités) de entity-framework après que le DbContext a été supprimé.

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 ont des 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 InvalidOperationExceptionlèvera car le dbContext n'a pas désactivé le chargement différé et le code accède à la propriété de navigation Pet après que le contexte a été supprimé par l'instruction using.

Débogage

Comment trouvez-vous la source de cette exception? En plus de regarder l'exception elle-même, qui sera lancée exactement à l'endroit où elle se produit, les règles générales de débogage dans Visual Studio s'appliquent: placez des points d'arrêt stratégiques et inspectez vos variables , soit en passant la souris sur leurs noms, en ouvrant un ( Rapide) Regardez la fenêtre ou en utilisant les différents panneaux de débogage comme Locals et Autos.

Si vous souhaitez savoir où se trouve ou non la référence, 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'arrête sur un tel point d'arrêt, vous devez déterminer si votre propriété de navigation doit avoir été remplie ou si les données demandées sont nécessaires.

Façons d'éviter

Désactiver le chargement différé

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

Avantages: au lieu de lancer l'exception InvalidOperationException, la propriété sera nulle. L'accès aux propriétés de null ou la tentative de modification des propriétés de cette propriété lèvera une NullReferenceException .

Comment demander explicitement l'objet en cas de besoin:

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 seul appel à la base de données. (Cependant, il peut également y avoir d'énormes problèmes de performances en fonction du nombre de résultats renvoyés et du nombre de propriétés de navigation demandées.Dans ce cas, il n'y aurait aucune pénalité de performances car les deux instances ne sont qu'un seul enregistrement et une seule 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 effectuant un appel supplémentaire à la base de données. Par défaut, Entity Framework suit les objets qu'il a récupérés de la base de données et s'il trouve des propriétés de navigation qui correspondent, il remplira automatiquement ces entités. Dans ce cas, comme le PetIdsur l' Personobjet correspond à Pet.Id, Entity Framework affectera le Person.Petà la Petvaleur récupérée, avant que la valeur ne soit affectée à la variable familière.

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

Erik Philips
la source
13

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;
Ricardo Pontual
la source
Pour moi, StackOverflow fonctionne à merveille avec une doublure. Et cela l'a fait pour moi, bravo à vous!
Harold_Finch
L'inconvénient est que vous devez utiliser .Include et des choses comme ça pour charger les propriétés de navigation.
boylec1986
1

Dans mon cas, je passais tous les modèles 'Users' à la colonne et il n'était pas mappé correctement, donc je viens de passer 'Users.Name' et cela l'a 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();
Michael Mora Montero
la source
1

La plupart des autres réponses indiquent un chargement impatient, mais j'ai trouvé une autre solution.

Dans mon cas, j'avais un objet EF InventoryItemavec une collection d' InvActivityobjets enfants.

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 enfant au lieu d'une requête de contexte (avec IQueryable), leInclude() fonction n'était pas disponible pour implémenter un chargement hâtif. Donc, à la place, ma solution était de créer un contexte à partir duquel j'ai utilisé GetLatestActivity()et attach()l'objet retourné:

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.

Zorgarath
la source
C'est fondamentalement un chargement impatient, vous avez chargé l'objet via un contexte. Il n'y a que deux options; chargement impatient et chargement paresseux.
Erik Philips
@ErikPhilips à droite, c'est un chargement paresseux avec un nouveau contexte de données
Zorgarath
1
@ErikPhilips - il y a aussi un chargement explicite - docs.microsoft.com/en-us/ef/ef6/querying/…
Dave Black
1

Si vous utilisez ASP.NET Core et que vous vous demandez pourquoi vous recevez ce message dans l'une de vos méthodes de contrôleur asynchrone, assurez-vous de renvoyer un Taskplutôt quevoid - ASP.NET Core supprime les contextes injectés.

(Je poste cette réponse car cette question est en haut dans les résultats de recherche pour ce message d'exception et c'est un problème subtil - peut-être est-ce utile aux personnes qui recherchent sur Google.)

John
la source