Risoluzione del problema "L'istanza di ObjectContext è stata eliminata e non può più essere utilizzata per operazioni che richiedono una connessione" InvalidOperationException


123

Sto provando a popolare un GridViewutilizzando Entity Frameworkm ma ogni volta che ricevo il seguente errore:

"La funzione di accesso della proprietà 'LoanProduct' sull'oggetto 'COSIS_DAL.MemberLoan' ha generato la seguente eccezione: L'istanza di ObjectContext è stata eliminata e non può più essere utilizzata per operazioni che richiedono una connessione."

Il mio codice è:

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'errore sta menzionando la LoanProductNamecolonna del Gridview. Menzionato: sto usando C #, ASP.net, SQL-Server 2008 come database back-end.

Sono abbastanza nuovo in Entity Framework. Non riesco a capire perché ricevo questo errore. Qualcuno mi può aiutare per favore?


1
Stai accedendo a qualche proprietà di navigazione nel gridview. Se lo fai, devi includere anche quelle tabelle di navigazione nella query. Mi piacequery.Include("SomeOtherTable")
Nilesh

Prova a creare una classe proxy per ospitare la tua entità o almeno a restituire un oggetto anonimo. Dal mio punto di vista, l'utilizzo di ef richiede la creazione di classi proxy per implementare le proprie logiche, utilizzare edmx proprio come il livello di accesso db non come business.
Gonzix

sì nel gridview ricevo anche un'altra colonna della tabella. Che è LoanProviderName.
Barsan

1
Prova a db.MemberLoans.Include("LoanProduct").OrderByDescending()controllare la sintassi perché non ho VS davanti a me.
Nilesh

3
Devi solo continuare a includere tutte le proprietà di navigazione a cui stai accedendo al di fuori del contesto db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Controlla le risposte di @Tragedian e @lazyberezovsky
Nilesh

Risposte:


175

Per impostazione predefinita, Entity Framework usa il caricamento lento per le proprietà di navigazione. Ecco perché queste proprietà devono essere contrassegnate come virtuali: EF crea la classe proxy per l'entità e sovrascrive le proprietà di navigazione per consentire il caricamento lento. Ad esempio, se hai questa entità:

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

Entity Framework restituirà il proxy ereditato da questa entità e fornirà l'istanza di DbContext a questo proxy per consentire il caricamento lento dell'appartenenza in un secondo momento:

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

Quindi, l'entità ha un'istanza di DbContext che è stata utilizzata per caricare l'entità. Questo è il tuo problema. Hai usingbloccato l'utilizzo di CosisEntities. Che elimina il contesto prima che le entità vengano restituite. Quando un po 'di codice in seguito tenta di utilizzare la proprietà di navigazione con caricamento lento, non riesce, perché il contesto viene eliminato in quel momento.

Per correggere questo comportamento puoi usare il caricamento ansioso delle proprietà di navigazione di cui avrai bisogno in seguito:

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

Ciò precaricherà tutte le iscrizioni e il caricamento lento non verrà utilizzato. Per i dettagli, vedere l' articolo sul caricamento di entità correlate su MSDN.


Grazie mille per la tua utile spiegazione e risposta. In realtà qui sto includendo tre tabelle, quindi non so come aggiungere le tre tabelle con INCLUDE. puoi per favore aiutarmi su questo per favore.
Barsan

8
@barsan include semplicemente tutte le proprietà di navigazione una per una. Ad esempio, db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);questo genererà una query JOIN e restituirà tutti i dati contemporaneamente.
Sergey Berezovskiy

1
Grazie mille lazyberezovsky. Ti sono molto grato. Mi hai salvato quasi un giorno. Dalla tua spiegazione sto imparando di più su Entity Framework. Grazie amico mio.
Barsan

Grazie amico, perfetto. Avevo un'istruzione using che limitava il caricamento lento. Bella risposta.
ncbl

4
Cosa succede se non voglio includere quelle entità correlate nella mia query?
Ortund

32

La CosisEntitiesclasse è tua DbContext. Quando crei un contesto in un fileusing blocco, stai definendo i confini per la tua operazione orientata ai dati.

Nel tuo codice, stai cercando di emettere il risultato di una query da un metodo e quindi terminare il contesto all'interno del metodo. L'operazione a cui si passa il risultato tenta quindi di accedere alle entità per popolare la visualizzazione griglia. Da qualche parte nel processo di associazione alla griglia, si accede a una proprietà con caricamento lento ed Entity Framework sta tentando di eseguire una ricerca per ottenere i valori. Non riesce, perché il contesto associato è già terminato.

Hai due problemi:

  1. Stai caricando in modo lento le entità quando ti colleghi alla griglia. Ciò significa che stai eseguendo molte operazioni di query separate su SQL Server, che rallenteranno tutto. È possibile risolvere questo problema rendendo le proprietà correlate caricate con entusiasmo per impostazione predefinita o chiedendo a Entity Framework di includerle nei risultati di questa query utilizzando il Includemetodo di estensione.

  2. Stai terminando il tuo contesto prematuramente: a DbContextdovrebbe essere disponibile per tutta l'unità di lavoro che viene eseguita, eliminandola solo quando hai finito con il lavoro a portata di mano. Nel caso di ASP.NET, un'unità di lavoro è in genere la richiesta HTTP gestita.


Grazie mille per le informazioni utili e la bella spiegazione del problema. In realtà sono così nuovo in Entity Framework così come in Linq, quindi queste informazioni sono davvero una grande lezione da imparare.
Barsan

20

Linea di fondo

Il codice ha recuperato i dati (entità) tramite il framework di entità con il caricamento lento abilitato e dopo che DbContext è stato eliminato, il codice fa riferimento a proprietà (entità correlate / relazione / navigazione) che non erano state richieste esplicitamente.

Più specificamente

Il InvalidOperationExceptioncon questo messaggio significa sempre la stessa cosa: stai richiedendo dati (entità) da entity-framework dopo che DbContext è stato eliminato.

Un semplice caso:

(queste classi verranno utilizzate per tutti gli esempi in questa risposta e presuppongono che tutte le proprietà di navigazione siano state configurate correttamente e abbiano tabelle associate nel database)

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

L'ultima riga genererà il InvalidOperationExceptionperché dbContext non ha disabilitato il caricamento lento e il codice accede alla proprietà di navigazione Pet dopo che Context è stato eliminato dall'istruzione using.

Debug

Come trovi la fonte di questa eccezione? Oltre a guardare l'eccezione stessa, che verrà lanciata esattamente nel punto in cui si verifica, si applicano le regole generali di debug in Visual Studio: posiziona punti di interruzione strategici e ispeziona le tue variabili , passando il mouse sui loro nomi, aprendo un ( Veloce) Finestra di controllo o utilizzo dei vari pannelli di debug come Locals e Autos.

Se vuoi scoprire dove si trova o non è impostato il riferimento, fai clic con il pulsante destro del mouse sul suo nome e seleziona "Trova tutti i riferimenti". È quindi possibile inserire un punto di interruzione in ogni posizione che richiede dati ed eseguire il programma con il debugger collegato. Ogni volta che il debugger si interrompe su un tale punto di interruzione, è necessario determinare se la proprietà di navigazione avrebbe dovuto essere popolata o se i dati richiesti sono necessari.

Modi per evitare

Disabilita il caricamento lento

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

Pro: invece di lanciare InvalidOperationException, la proprietà sarà null. L'accesso a proprietà di null o il tentativo di modificare le proprietà di questa proprietà genererà un'eccezione NullReferenceException .

Come richiedere esplicitamente l'oggetto quando necessario:

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

Nell'esempio precedente, Entity Framework materializzerà il Pet oltre alla Persona. Questo può essere vantaggioso perché è una singola chiamata al database. (Tuttavia, possono anche verificarsi enormi problemi di prestazioni a seconda del numero di risultati restituiti e del numero di proprietà di navigazione richieste, in questo caso non ci sarebbe alcuna penalizzazione delle prestazioni perché entrambe le istanze sono solo un singolo record e un singolo join).

o

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

Nell'esempio precedente, Entity Framework materializzerà il Pet indipendentemente dalla Persona effettuando una chiamata aggiuntiva al database. Per impostazione predefinita, le tracce di Entity Framework oggetti che ha recuperato dal database e se trova le proprietà di navigazione che partita sarà auto-magicamente popolare queste entità. In questo esempio perché il PetIdsul Personoggetto corrisponde al Pet.Id, Entity Framework assegna il Person.Petal Petvalore recuperato, prima che il valore viene assegnato alla variabile pet.

Consiglio sempre questo approccio in quanto costringe i programmatori a capire quando e come il codice richiede i dati tramite Entity Framework. Quando il codice genera un'eccezione di riferimento null su una proprietà di un'entità, puoi quasi sempre essere sicuro di non aver richiesto esplicitamente quei dati.


13

È una risposta molto tardiva ma ho risolto il problema disattivando il caricamento lento:

db.Configuration.LazyLoadingEnabled = false;

Per me, StackOverflow fa miracoli con una riga. E questo lo ha fatto per me, complimenti a te!
Harold_Finch

Lo svantaggio è che devi usare .Include e cose del genere per caricare le proprietà di navigazione.
boylec1986

1

Nel mio caso, stavo passando tutti i modelli "Users" alla colonna e non era mappato correttamente, quindi ho semplicemente passato "Users.Name" e lo ha risolto.

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

La maggior parte delle altre risposte indicano un caricamento desideroso, ma ho trovato un'altra soluzione.

Nel mio caso avevo un oggetto EF InventoryItemcon una raccolta di InvActivityoggetti figlio.

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

E poiché stavo estraendo dalla raccolta di oggetti figlio invece di una query di contesto (con IQueryable), la Include()funzione non era disponibile per implementare il caricamento desideroso. Quindi invece la mia soluzione è stata quella di creare un contesto da cui ho utilizzato GetLatestActivity()e attach()l'oggetto restituito:

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

Quindi non sei bloccato con il caricamento desideroso.


Questo è fondamentalmente un caricamento desideroso, hai caricato l'oggetto tramite un contesto. Ci sono solo due opzioni; caricamento desideroso e caricamento pigro.
Erik Philips

@ErikPhilips esatto, è un caricamento lento con un nuovo contesto dati
Zorgarath

1
@ErikPhilips - c'è anche il caricamento esplicito - docs.microsoft.com/en-us/ef/ef6/querying/…
Dave Black,

1

Se stai usando ASP.NET Core e ti chiedi perché ricevi questo messaggio in uno dei metodi del tuo controller asincrono, assicurati di restituire un Taskinvece divoid - ASP.NET Core dispone dei contesti iniettati.

(Sto postando questa risposta poiché questa domanda è in cima ai risultati di ricerca di quel messaggio di eccezione ed è un problema sottile, forse è utile per le persone che lo utilizzano su Google.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.