Lösen von "Die ObjectContext-Instanz wurde entsorgt und kann nicht mehr für Vorgänge verwendet werden, für die eine Verbindung erforderlich ist" InvalidOperationException

122

Ich versuche, ein GridViewmit Entity Frameworkm zu füllen, aber jedes Mal erhalte ich den folgenden Fehler:

"Der Eigenschaftenzugriff 'LoanProduct' für das Objekt 'COSIS_DAL.MemberLoan' hat die folgende Ausnahme ausgelöst: Die ObjectContext-Instanz wurde entsorgt und kann nicht mehr für Vorgänge verwendet werden, für die eine Verbindung erforderlich ist."

Mein Code lautet:

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

Der Fehler erwähnt die LoanProductNameSpalte der Gridview. Erwähnt: Ich verwende C #, ASP.net, SQL-Server 2008 als Back-End-Datenbank.

Ich bin ziemlich neu in Entity Framework. Ich kann nicht verstehen, warum ich diesen Fehler erhalte. Kann mir bitte jemand helfen?

Barsan
quelle
1
Greifen Sie auf Navigationseigenschaften in der Rasteransicht zu? In diesem Fall müssen Sie diese Navigationstabellen auch in die Abfrage aufnehmen. Gefällt query.Include("SomeOtherTable")
mir
Versuchen Sie entweder, eine Proxy-Klasse zum Hosten Ihrer Entität zu erstellen oder zumindest ein anonymes Objekt zurückzugeben. Aus meiner Sicht erfordert die Verwendung von ef das Erstellen von Proxy-Klassen zum Implementieren Ihrer Logik. Verwenden Sie edmx nur als Datenbankzugriffsschicht und nicht als Geschäft.
Gonzix
Ja, in der Rasteransicht erhalte ich auch eine andere Tabellenspalte. Welches ist LoanProviderName.
Barsan
1
Versuchen Sie db.MemberLoans.Include("LoanProduct").OrderByDescending()die Syntax überprüfen , weil ich VS vor mir nicht haben.
Nilesh
3
Sie müssen lediglich alle Navigationseigenschaften einschließen, auf die Sie außerhalb des Kontexts zugreifen, z db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Überprüfen Sie die Antworten von @Tragedian und @lazyberezovsky
Nilesh

Antworten:

173

Standardmäßig verwendet Entity Framework das verzögerte Laden für Navigationseigenschaften. Aus diesem Grund sollten diese Eigenschaften als virtuell markiert werden. EF erstellt eine Proxy-Klasse für Ihre Entität und überschreibt die Navigationseigenschaften, um ein verzögertes Laden zu ermöglichen. ZB wenn Sie diese Entität haben:

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

Das Entity Framework gibt den von dieser Entität geerbten Proxy zurück und stellt diesem Proxy die DbContext-Instanz zur Verfügung, um später ein verzögertes Laden der Mitgliedschaft zu ermöglichen:

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

Die Entität verfügt also über eine Instanz von DbContext, die zum Laden der Entität verwendet wurde. Das ist dein Problem. Sie haben usingdie Verwendung von CosisEntities blockiert. Womit der Kontext gelöscht wird, bevor Entitäten zurückgegeben werden. Wenn ein Code später versucht, eine verzögert geladene Navigationseigenschaft zu verwenden, schlägt dies fehl, da der Kontext in diesem Moment verfügbar ist.

Um dieses Verhalten zu beheben, können Sie die Navigationseigenschaften, die Sie später benötigen, eifrig laden:

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

Dadurch werden alle Mitgliedschaften vorab geladen, und das verzögerte Laden wird nicht verwendet. Weitere Informationen finden Sie im Artikel Laden verwandter Entitäten in MSDN.

Sergey Berezovskiy
quelle
Vielen Dank für Ihre hilfreiche Erklärung und Antwort. Eigentlich füge ich hier drei Tabellen hinzu, daher weiß ich nicht, wie ich die drei Tabellen mit INCLUDE hinzufügen kann. Kannst du mir bitte dabei helfen?
Barsan
8
@barsan enthält nur alle Navigationseigenschaften nacheinander. Dies db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);generiert beispielsweise eine JOIN-Abfrage und gibt alle Daten auf einmal zurück.
Sergey Berezovskiy
1
Vielen Dank Lazyberezovsky. Ich bin dir so dankbar. Du hast mich fast einen Tag gerettet. Aus Ihrer Erklärung erfahre ich mehr über Entity Framework. Danke mein Freund.
Barsan
Danke Kumpel, perfekt. Ich hatte eine using-Anweisung, die das verzögerte Laden einschränkte. Gute Antwort.
ncbl
4
Was ist, wenn ich diese verwandten Entitäten überhaupt nicht in meine Abfrage aufnehmen möchte?
Ortund
32

Die CosisEntitiesKlasse ist deine DbContext. Wenn Sie einen Kontext in einem usingBlock erstellen , definieren Sie die Grenzen für Ihre datenorientierte Operation.

In Ihrem Code versuchen Sie, das Ergebnis einer Abfrage von einer Methode auszugeben und dann den Kontext innerhalb der Methode zu beenden. Die Operation, an die Sie das Ergebnis übergeben, versucht dann, auf die Entitäten zuzugreifen, um die Rasteransicht zu füllen. Irgendwann beim Binden an das Raster wird auf eine verzögert geladene Eigenschaft zugegriffen, und Entity Framework versucht, eine Suche durchzuführen, um die Werte abzurufen. Es schlägt fehl, da der zugehörige Kontext bereits beendet wurde.

Sie haben zwei Probleme:

  1. Sie laden Entitäten faul, wenn Sie an das Raster binden. Dies bedeutet, dass Sie viele separate Abfragevorgänge für SQL Server ausführen, die alles verlangsamen. Sie können dieses Problem beheben, indem Sie entweder die zugehörigen Eigenschaften standardmäßig eifrig laden oder Entity Framework auffordern, sie mithilfe der IncludeErweiterungsmethode in die Ergebnisse dieser Abfrage aufzunehmen .

  2. Sie beenden Ihren Kontext vorzeitig: a DbContextsollte in der gesamten ausgeführten Arbeitseinheit verfügbar sein und erst dann verfügbar sein, wenn Sie mit der vorliegenden Arbeit fertig sind. Im Fall von ASP.NET ist eine Arbeitseinheit normalerweise die HTTP-Anforderung, die verarbeitet wird.

Paul Turner
quelle
Vielen Dank für die nützlichen Informationen und die nette Erklärung des Problems. Eigentlich bin ich sowohl in Entity Framework als auch in Linq so neu, dass diese Informationen wirklich eine großartige Lektion für mich sind.
Barsan
20

Endeffekt

Ihr Code hat Daten (Entitäten) über das Entitätsframework mit aktiviertem verzögertem Laden abgerufen. Nachdem der DbContext entsorgt wurde, verweist Ihr Code auf Eigenschaften (verwandte / Beziehungs- / Navigationsentitäten), die nicht explizit angefordert wurden.

Genauer

Das InvalidOperationExceptionmit dieser Nachricht bedeutet immer dasselbe: Sie fordern Daten (Entitäten) vom Entity-Framework an, nachdem der DbContext entsorgt wurde.

Ein einfacher Fall:

(Diese Klassen werden für alle Beispiele in dieser Antwort verwendet und setzen voraus, dass alle Navigationseigenschaften korrekt konfiguriert wurden und Tabellen in der Datenbank zugeordnet sind.)

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

In der letzten Zeile wird InvalidOperationExceptiondas ausgegeben, da der dbContext das verzögerte Laden nicht deaktiviert hat und der Code auf die Pet-Navigationseigenschaft zugreift, nachdem der Kontext durch die using-Anweisung entsorgt wurde.

Debuggen

Wie finden Sie die Quelle dieser Ausnahme? Abgesehen von der Betrachtung der Ausnahme selbst, die genau an der Stelle ausgelöst wird, an der sie auftritt, gelten die allgemeinen Regeln für das Debuggen in Visual Studio: Platzieren Sie strategische Haltepunkte und überprüfen Sie Ihre Variablen , indem Sie entweder mit der Maus über deren Namen fahren und ein ( Schnell) Beobachten Sie das Fenster oder verwenden Sie die verschiedenen Debugging-Bereiche wie Locals und Autos.

Wenn Sie herausfinden möchten, wo die Referenz festgelegt ist oder nicht, klicken Sie mit der rechten Maustaste auf den Namen und wählen Sie "Alle Referenzen suchen". Sie können dann an jedem Speicherort, an dem Daten angefordert werden, einen Haltepunkt setzen und Ihr Programm mit dem angehängten Debugger ausführen. Jedes Mal, wenn der Debugger an einem solchen Haltepunkt unterbrochen wird, müssen Sie feststellen, ob Ihre Navigationseigenschaft ausgefüllt werden sollte oder ob die angeforderten Daten erforderlich sind.

Möglichkeiten zu vermeiden

Lazy-Loading deaktivieren

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

Vorteile: Anstatt die InvalidOperationException auszulösen, ist die Eigenschaft null. Wenn Sie auf Eigenschaften von null zugreifen oder versuchen, die Eigenschaften dieser Eigenschaft zu ändern, wird eine NullReferenceException ausgelöst .

So fordern Sie das Objekt bei Bedarf explizit an:

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

Im vorherigen Beispiel materialisiert das Entity Framework das Haustier zusätzlich zur Person. Dies kann vorteilhaft sein, da es sich um einen einzelnen Aufruf der Datenbank handelt. (Abhängig von der Anzahl der zurückgegebenen Ergebnisse und der Anzahl der angeforderten Navigationseigenschaften kann es jedoch auch zu großen Leistungsproblemen kommen. In diesem Fall würde es keine Leistungseinbußen geben, da beide Instanzen nur ein einzelner Datensatz und ein einzelner Join sind.)

oder

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

Im vorherigen Beispiel materialisiert Entity Framework das Haustier unabhängig von der Person, indem es die Datenbank zusätzlich aufruft. Standardmäßig verfolgt Entity Framework Objekte, die aus der Datenbank abgerufen wurden, und füllt diese Entitäten automatisch auf magische Weise, wenn passende Navigationseigenschaften gefunden werden . In diesem Fall , da die PetIdauf dem PersonObjekt entspricht das Pet.Idwird Entity Framework zuweisen die Person.Petauf den PetWert abgerufen, bevor der Wert für das Haustier Variablen zugewiesen wird.

Ich empfehle diesen Ansatz immer, da er Programmierer dazu zwingt zu verstehen, wann und wie Code Daten über Entity Framework anfordert. Wenn Code eine Nullreferenzausnahme für eine Eigenschaft einer Entität auslöst, können Sie fast immer sicher sein, dass Sie diese Daten nicht explizit angefordert haben.

Erik Philips
quelle
13

Es ist eine sehr späte Antwort, aber ich habe das Problem behoben, durch das das verzögerte Laden deaktiviert wurde:

db.Configuration.LazyLoadingEnabled = false;
Ricardo Pontual
quelle
Für mich wirkt StackOverflow mit einem Liner Wunder. Und das hat es für mich getan, ein großes Lob an Sie!
Harold_Finch
Nachteil ist, dass Sie .Include und ähnliche Dinge verwenden müssen, um Navigationseigenschaften zu laden.
Boylec1986
1

In meinem Fall habe ich alle Modelle 'Benutzer' an die Spalte übergeben und es wurde nicht richtig zugeordnet, also habe ich nur 'Benutzer.Name' übergeben und es wurde behoben.

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
quelle
1

Die meisten anderen Antworten deuten auf eifriges Laden hin, aber ich habe eine andere Lösung gefunden.

In meinem Fall hatte ich ein EF-Objekt InventoryItemmit einer Sammlung von InvActivityuntergeordneten Objekten.

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

Und da ich anstelle einer Kontextabfrage (mit IQueryable) aus der untergeordneten Objektsammlung gezogen habe , war die Include()Funktion nicht verfügbar, um eifriges Laden zu implementieren. Anstatt also war meine Lösung , die einen Zusammenhang von zu schaffen , wo ich genutzt GetLatestActivity()und attach()das zurückgegebene Objekt:

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

So bleiben Sie nicht beim eifrigen Laden hängen.

Zorgarath
quelle
Dies ist im Grunde ein eifriges Laden. Sie haben das Objekt über einen Kontext geladen. Es gibt nur zwei Möglichkeiten; eifriges Laden und faules Laden.
Erik Philips
@ErikPhilips richtig, es ist faul Laden mit einem neuen Datenkontext
Zorgarath
1
@ErikPhilips - es gibt auch explizites Laden - docs.microsoft.com/en-us/ef/ef6/querying/…
Dave Black
1

Wenn Sie ASP.NET Core verwenden und sich fragen, warum diese Meldung in einer Ihrer asynchronen Controller-Methoden angezeigt wird, stellen Sie sicher, dass Sie einen Tasknicht void- ASP.NET Core zurückgeben, der injizierte Kontexte bereitstellt.

(Ich poste diese Antwort, da diese Frage in den Suchergebnissen zu dieser Ausnahmemeldung hoch ist und ein subtiles Problem darstellt. Vielleicht ist sie für Leute nützlich, die Google dafür verwenden.)

John
quelle