Rozwiązywanie problemu „Instancja ObjectContext została usunięta i nie może być już używana do operacji wymagających połączenia” InvalidOperationException

123

Próbuję wypełnić GridViewprzy użyciu Entity Frameworkm, ale za każdym razem otrzymuję następujący błąd:

„Akcesor właściwości„ LoanProduct ”na obiekcie„ COSIS_DAL.MemberLoan ”spowodował następujący wyjątek: Instancja ObjectContext została usunięta i nie może być już używana do operacji wymagających połączenia."

Mój kod to:

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

Błąd zawiera wzmiankę o LoanProductNamekolumnie Gridview. Wspomniane: używam C #, ASP.net, SQL-Server 2008 jako zaplecza DB.

Jestem całkiem nowy w Entity Framework. Nie mogę zrozumieć, dlaczego otrzymuję ten błąd. Czy ktoś może mi pomóc, proszę?

Barsan
źródło
1
Czy uzyskujesz dostęp do właściwości nawigacji w widoku siatki. Jeśli tak, musisz również uwzględnić te tabele nawigacyjne w zapytaniu. Na przykładquery.Include("SomeOtherTable")
Nilesh
Spróbuj utworzyć klasę proxy do hostowania Twojej encji lub przynajmniej zwrócić anonimowy obiekt. Z mojego punktu widzenia używanie ef wymaga tworzenia klas proxy do implementacji logiki, używaj edmx tak samo, jak warstwy dostępu do bazy danych, a nie jako biznes.
Gonzix
tak, w widoku siatki otrzymuję również kolejną kolumnę tabeli. Co to jest LoanProviderName.
Barsan
1
Spróbuj db.MemberLoans.Include("LoanProduct").OrderByDescending()sprawdzić składnię, ponieważ nie mam przed sobą VS.
Nilesh
3
Musisz tylko kontynuować, włączając wszystkie właściwości nawigacji, do których uzyskujesz dostęp poza kontekstem, takim jak db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Sprawdź odpowiedzi @Tragedian i @lazyberezovsky
Nilesh

Odpowiedzi:

175

Domyślnie Entity Framework używa ładowania z opóźnieniem dla właściwości nawigacji. Dlatego te właściwości powinny być oznaczone jako wirtualne - EF tworzy klasę proxy dla Twojej jednostki i zastępuje właściwości nawigacji, aby umożliwić ładowanie z opóźnieniem. Np. Jeśli masz ten podmiot:

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

Entity Framework zwróci serwer proxy dziedziczony z tej jednostki i zapewni wystąpienie DbContext do tego serwera proxy, aby umożliwić późniejsze ładowanie członkostwa z opóźnieniem:

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

Tak więc jednostka ma wystąpienie DbContext, które zostało użyte do załadowania jednostki. To Twój problem. Masz usingblokadę wokół wykorzystania CosisEntities. Który usuwa kontekst przed zwróceniem jednostek. Gdy jakiś kod później próbuje użyć właściwości nawigacji ładowanej z opóźnieniem, kończy się to niepowodzeniem, ponieważ kontekst jest usuwany w tym momencie.

Aby naprawić to zachowanie, możesz użyć szybkiego ładowania właściwości nawigacji, które będą potrzebne później:

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

Spowoduje to wstępne załadowanie wszystkich członkostw, a ładowanie z opóźnieniem nie będzie używane. Aby uzyskać szczegółowe informacje, zobacz artykuł Ładowanie powiązanych jednostek w witrynie MSDN.

Sergey Berezovskiy
źródło
Bardzo dziękuję za pomocne wyjaśnienie i odpowiedź. Właściwie tutaj uwzględniam trzy tabele, więc nie wiem, jak mogę dodać trzy tabele za pomocą INCLUDE. czy możesz mi w tym pomóc, proszę.
Barsan
8
@barsan zawiera po kolei wszystkie właściwości nawigacji. Np. db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);To wygeneruje zapytanie JOIN i zwróci wszystkie dane naraz.
Sergey Berezovskiy
1
Wielkie dzięki lazyberezovsky. Jestem ci bardzo wdzięczny. Uratowałeś mi prawie dzień. Z twojego wyjaśnienia dowiaduję się więcej o Entity Framework. Dziękuję, przyjacielu.
Barsan
Dzięki stary, doskonale. Miałem instrukcję using, która ograniczała leniwe ładowanie. Świetna odpowiedź.
ncbl
4
Co się stanie, jeśli w ogóle nie chcę uwzględniać tych powiązanych jednostek w moim zapytaniu?
Ortund
32

CosisEntitiesKlasa jest Twoja DbContext. Tworząc kontekst w usingbloku, definiujesz granice operacji zorientowanej na dane.

W swoim kodzie próbujesz wyemitować wynik zapytania z metody, a następnie zakończyć kontekst w metodzie. Operacja, do której przekazujesz wynik, próbuje następnie uzyskać dostęp do jednostek w celu wypełnienia widoku siatki. Gdzieś w trakcie tworzenia powiązania z siatką uzyskuje się dostęp do właściwości ładowanej z opóźnieniem, a Entity Framework próbuje przeprowadzić wyszukiwanie w celu uzyskania wartości. Nie udaje się, ponieważ powiązany kontekst już się zakończył.

Masz dwa problemy:

  1. Leniwie ładujesz jednostki, gdy łączysz się z siatką. Oznacza to, że wykonujesz wiele oddzielnych operacji zapytań w SQL Server, co spowolni wszystko. Możesz rozwiązać ten problem, domyślnie ładując powiązane właściwości z przyspieszeniem lub prosząc Entity Framework o uwzględnienie ich w wynikach tego zapytania przy użyciu Includemetody rozszerzenia.

  2. Przedwcześnie kończysz swój kontekst: a DbContextpowinien być dostępny przez całą wykonywaną jednostkę pracy, usuwając go dopiero po zakończeniu pracy. W przypadku ASP.NET jednostką pracy jest zwykle obsługiwane żądanie HTTP.

Paul Turner
źródło
Bardzo dziękuję za przydatne informacje i miłe wyjaśnienie problemu. Właściwie jestem nowy w Entity Framework, a także w Linq, więc ta informacja jest dla mnie naprawdę świetną lekcją.
Barsan
20

Podsumowanie

Twój kod pobrał dane (jednostki) za pośrednictwem platformy jednostek z włączonym ładowaniem z opóźnieniem, a po usunięciu DbContext kod odwołuje się do właściwości (jednostek powiązanych / relacji / nawigacji), które nie zostały jawnie zażądane.

Dokładniej

Z InvalidOperationExceptiontym komunikatem zawsze oznacza to samo: żądasz danych (jednostek) z platformy encji po usunięciu DbContext.

Prosty przypadek:

(te klasy będą używane we wszystkich przykładach w tej odpowiedzi, przy założeniu, że wszystkie właściwości nawigacji zostały poprawnie skonfigurowane i mają powiązane tabele w bazie danych)

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

Ostatni wiersz wyrzuci błąd, InvalidOperationExceptionponieważ dbContext nie wyłączył ładowania z opóźnieniem, a kod uzyskuje dostęp do właściwości nawigacji Pet po usunięciu Context przez instrukcję using.

Debugowanie

Jak znajdujesz źródło tego wyjątku? Oprócz spojrzenia na sam wyjątek, który zostanie wyrzucony dokładnie w miejscu, w którym wystąpi, obowiązują ogólne zasady debugowania w programie Visual Studio: umieszczaj strategiczne punkty przerwania i sprawdzaj zmienne , albo najeżdżając kursorem myszy na ich nazwy, otwierając znak ( Szybkie) Okno obserwacyjne lub za pomocą różnych paneli debugowania, takich jak Lokalne i Auta.

Jeśli chcesz dowiedzieć się, gdzie odniesienie jest lub nie jest ustawione, kliknij prawym przyciskiem myszy jego nazwę i wybierz „Znajdź wszystkie odwołania”. Następnie możesz umieścić punkt przerwania w każdej lokalizacji, która żąda danych, i uruchomić program z dołączonym debugerem. Za każdym razem, gdy debuger przerywa pracę w takim punkcie przerwania, należy określić, czy właściwość nawigacji powinna zostać wypełniona, czy też wymagane są wymagane dane.

Sposoby uniknięcia

Wyłącz leniwe ładowanie

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

Zalety: zamiast zgłaszać wyjątek InvalidOperationException, właściwość będzie miała wartość null. Uzyskiwanie dostępu do właściwości null lub próba zmiany właściwości tej właściwości spowoduje zgłoszenie wyjątku NullReferenceException .

Jak jawnie zażądać obiektu w razie potrzeby:

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

W poprzednim przykładzie Entity Framework zmaterializuje Pet oprócz Person. Może to być korzystne, ponieważ jest to pojedyncze wywołanie bazy danych. (Jednak w zależności od liczby zwracanych wyników i liczby żądanych właściwości nawigacji mogą również wystąpić ogromne problemy z wydajnością, w tym przypadku nie byłoby spadku wydajności, ponieważ oba wystąpienia są tylko jednym rekordem i pojedynczym złączeniem).

lub

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

W poprzednim przykładzie Entity Framework zmaterializuje Pet niezależnie od osoby, wykonując dodatkowe wywołanie bazy danych. Domyślnie Entity Framework śledzi obiekty pobrane z bazy danych i jeśli znajdzie właściwości nawigacji, które pasują do niego, automatycznie wypełni te jednostki. W tym przypadku, ponieważ PetIdna Personobiekcie dopasowuje Pet.Id, Entity Framework przypisze Person.Petdo Petwartości pobranej przed wartość jest przypisana do zmiennej domowych.

Zawsze polecam to podejście, ponieważ zmusza programistów do zrozumienia, kiedy i jak kod żąda danych za pośrednictwem Entity Framework. Gdy kod zgłasza wyjątek odwołania zerowego dla właściwości jednostki, prawie zawsze można mieć pewność, że nie zażądano jawnie tych danych.

Erik Philips
źródło
13

To bardzo późna odpowiedź, ale rozwiązałem problem wyłączając leniwe ładowanie:

db.Configuration.LazyLoadingEnabled = false;
Ricardo Pontual
źródło
Dla mnie StackOverflow działa cuda z jednym wkładem. I to zrobiło to dla mnie, chwała dla ciebie!
Harold_Finch
Wadą jest to, że musisz użyć .Include i podobnych rzeczy, aby załadować właściwości nawigacji.
boylec1986
1

W moim przypadku przekazywałem wszystkie modele „Users” do kolumny i nie było to poprawnie zmapowane, więc po prostu przekazałem „Users.Name” i naprawiłem to.

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
źródło
1

Większość innych odpowiedzi wskazuje na chęć ładowania, ale znalazłem inne rozwiązanie.

W moim przypadku miałem obiekt EF InventoryItemz kolekcją InvActivityobiektów potomnych.

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

A ponieważ wyciągałem z kolekcji obiektów potomnych zamiast zapytania kontekstowego (z IQueryable), Include()funkcja nie była dostępna do zaimplementowania zachłannego ładowania. Zamiast tego moim rozwiązaniem było utworzenie kontekstu, z którego użyłem GetLatestActivity()i attach()zwróconego obiektu:

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

Dzięki temu nie utkniesz z gorliwym ładowaniem.

Zorgarath
źródło
To jest po prostu gwałtowne ładowanie, załadowałeś obiekt przez kontekst. Są tylko dwie opcje; chętne i leniwe ładowanie.
Erik Philips
@ErikPhilips racja, ładowanie z nowym kontekstem danych jest leniwe
Zorgarath
1
@ErikPhilips - istnieje również jawne ładowanie - docs.microsoft.com/en-us/ef/ef6/querying/ ...
Dave Black
1

Jeśli używasz ASP.NET Core i zastanawiasz się, dlaczego otrzymujesz ten komunikat w jednej z metod kontrolera asynchronicznego, upewnij się, że zwracasz Taskzamiast void- ASP.NET Core usuwa wstrzyknięte konteksty.

(Publikuję tę odpowiedź, ponieważ to pytanie jest wysoko w wynikach wyszukiwania tego komunikatu o wyjątku i jest to subtelny problem - może jest przydatny dla osób, które w tym celu wyszukują w Google.)

Jan
źródło