webentwicklung-frage-antwort-db.com.de

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

Ich versuche, ein GridView mit Entity Frameworkm zu füllen, erhalte jedoch jedes Mal die folgende Fehlermeldung:

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

Mein Code ist:

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 LoanProductName -Spalte von Gridview. Erwähnt: Ich verwende C #, ASP.net, SQL-Server 2008 als Back-End-DB.

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

111
barsan

Standardmäßig verwendet Entity Framework Lazy-Loading 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. Z.B. Wenn Sie diese Entität haben:

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

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 das verzögerte 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 hat also eine Instanz von DbContext, die zum Laden der Entität verwendet wurde. Das ist dein Problem. Sie haben using Block um die Verwendung von CosisEntities. Welcher Kontext verfügbar macht, bevor Entitäten zurückgegeben werden. Wenn später ein Code versucht, eine verzögert geladene Navigationseigenschaft zu verwenden, schlägt dies fehl, da der Kontext zu diesem Zeitpunkt freigegeben wird.

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

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

Dadurch werden alle Mitgliedschaften vorgeladen und Lazy-Loading wird nicht verwendet. Weitere Informationen finden Sie in Laden verwandter Entitäten Artikel über MSDN.

151

Die Klasse CosisEntities ist Ihre Klasse DbContext. Wenn Sie einen Kontext in einem using -Block 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 aufzufü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, weil der zugehörige Kontext bereits beendet wurde.

Sie haben zwei Probleme:

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

  2. Sie beenden Ihren Kontext vorzeitig: Ein DbContext sollte in der gesamten ausgeführten Arbeitseinheit verfügbar sein und nur 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.

27
Paul Turner

Endeffekt

Ihr Code hat Daten (Entitäten) über ein Entity-Framework mit aktiviertem Lazy-Loading abgerufen. Nach der Freigabe von DbContext verweist Ihr Code auf Eigenschaften (zugehörige/Beziehungs-/Navigations-Entitäten), die nicht explizit angefordert wurden.

Genauer

Das InvalidOperationException mit dieser Meldung bedeutet immer dasselbe: Sie fordern Daten (Entities) 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 die 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 InvalidOperationException ausgegeben, da der dbContext das verzögerte Laden nicht deaktiviert hat und der Code auf die Pet-Navigationseigenschaft zugreift, nachdem der Context durch die using-Anweisung entsorgt wurde.

Debuggen

Wie finden Sie die Quelle dieser Ausnahme? Abgesehen von der Ausnahme selbst, die genau an dem Ort ausgelöst wird, an dem 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 den Mauszeiger darüber halten Öffnen Sie ein (Quick) Watch-Fenster oder verwenden Sie die verschiedenen Debug-Fenster wie Locals und Autos.

Wenn Sie herausfinden möchten, wo sich die Referenz befindet oder nicht, klicken Sie mit der rechten Maustaste auf den Namen und wählen Sie "Alle Referenzen suchen". Anschließend können Sie an jedem Speicherort, an dem Daten angefordert werden, einen Haltepunkt einfügen und Ihr Programm mit dem angehängten Debugger ausführen. Bei jeder Unterbrechung des Debuggers an einem solchen Haltepunkt müssen Sie feststellen, ob Ihre Navigationseigenschaft ausgefüllt werden sollte oder ob die angeforderten Daten erforderlich sind.

Möglichkeiten zu vermeiden

Deaktivieren Sie Lazy-Loading

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 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 erheblichen Leistungsproblemen kommen. In diesem Fall würde es keine Leistungseinbußen geben, da beide Instanzen nur einen einzelnen Datensatz und einen einzelnen Join enthalten.).

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 es aus der Datenbank abgerufen hat. Wenn es passende Navigationseigenschaften findet, werden diese Entitäten automatisch und automatisch aufgefüllt. Da in diesem Fall das PetId im Person-Objekt mit dem Pet.Id Übereinstimmt, weist Entity Framework dem abgerufenen Pet-Wert den Person.Pet Zu , bevor der Wert der Haustiervariablen 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.

16
Erik Philips

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

db.Configuration.LazyLoadingEnabled = false;
7
Ricardo Pontual

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 ein Task anstelle von void zurückgeben - ASP.NET Core entsorgt injiziert Kontexte.

(Ich poste diese Antwort, da diese Frage in den Suchergebnissen zu dieser Ausnahmebedingungsnachricht hoch ist und ein subtiles Problem darstellt. Vielleicht ist sie für Google-Nutzer hilfreich.)

1
John

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 InventoryItem mit einer Sammlung von InvActivity untergeordneten 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 abgerufen habe, war die Funktion Include() nicht verfügbar, um das eifrige Laden zu implementieren. Stattdessen bestand meine Lösung darin, einen Kontext zu erstellen, aus dem ich GetLatestActivity() und attach() das zurückgegebene Objekt verwendete:

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 stecken Sie nicht mit eifrigem Laden fest.

1
Zorgarath

In meinem Fall habe ich alle Modelle als 'Benutzer' an die Spalte übergeben und sie wurde nicht richtig zugeordnet. Deshalb habe ich nur 'Benutzer.Name' übergeben und das Problem 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();