Решение «Экземпляр ObjectContext был удален и больше не может использоваться для операций, требующих подключения» InvalidOperationException

123

Я пытаюсь заполнить GridViewEntity Frameworkm, но каждый раз получаю следующую ошибку:

«Средство доступа к свойству LoanProduct на объекте COSIS_DAL.MemberLoan вызвало следующее исключение: экземпляр ObjectContext был удален и больше не может использоваться для операций, требующих подключения».

Мой код:

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

Ошибка упоминает LoanProductNameстолбец Gridview. Упоминается: я использую C #, ASP.net, SQL-Server 2008 в качестве серверной БД.

Я новичок в Entity Framework. Я не могу понять, почему я получаю эту ошибку. Кто-нибудь может мне помочь?

Бырсан
источник
1
Вы получаете доступ к каким-либо свойствам навигации в gridview. Если вы это сделаете, вам также необходимо включить эти таблицы навигации в запрос. Likequery.Include("SomeOtherTable")
Nilesh
Попробуйте либо создать прокси-класс для размещения вашей сущности, либо, по крайней мере, вернуть анонимный объект. С моей точки зрения, использование ef требует создания прокси-классов для реализации вашей логики, используйте edmx так же, как уровень доступа к базе данных, а не как бизнес.
Gonzix
да, в gridview я также получаю еще один столбец таблицы. Это LoanProviderName.
Барсан
1
Попробуйте db.MemberLoans.Include("LoanProduct").OrderByDescending()проверить синтаксис, потому что передо мной нет VS.
Nilesh
3
Вам просто нужно включить все свойства навигации, к которым вы обращаетесь вне контекста, например db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Проверьте ответы @Tragedian и @lazyberezovsky
Nilesh

Ответы:

175

По умолчанию Entity Framework использует отложенную загрузку для свойств навигации. Вот почему эти свойства должны быть помечены как виртуальные - EF создает прокси-класс для вашей сущности и переопределяет свойства навигации, чтобы разрешить отложенную загрузку. Например, если у вас есть эта сущность:

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

Entity Framework вернет прокси, унаследованный от этой сущности, и предоставит экземпляр DbContext этому прокси, чтобы позже разрешить ленивую загрузку членства:

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

Итак, у объекта есть экземпляр DbContext, который использовался для загрузки объекта. Это твоя проблема. Вы usingзаблокировали использование CosisEntities. Который удаляет контекст перед возвратом сущностей. Когда позже какой-то код пытается использовать свойство навигации с отложенной загрузкой, он терпит неудачу, потому что в этот момент контекст удаляется.

Чтобы исправить это поведение, вы можете использовать активную загрузку свойств навигации, которые вам понадобятся позже:

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

Это предварительно загрузит все членства, и ленивая загрузка не будет использоваться. Подробнее см. Статью Загрузка связанных объектов на MSDN.

Сергей Березовский
источник
Большое спасибо за полезное объяснение и ответ. На самом деле здесь я включаю три таблицы, поэтому я не знаю, как добавить три таблицы с помощью INCLUDE. не могли бы вы помочь мне в этом, пожалуйста.
Барсан
8
@barsan просто включает все свойства навигации по одному. Например db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);, это сгенерирует запрос JOIN и вернет все данные сразу.
Сергей Березовский
1
Большое спасибо lazyberezovsky. Я так вам благодарен. Вы сэкономили мне почти день. Из вашего объяснения я узнаю больше о Entity Framework. Спасибо, мой друг.
Барсан
Спасибо, дружище, отлично. У меня был оператор using, который ограничивал ленивую загрузку. Отличный ответ.
ncbl 08
4
Что, если я вообще не хочу включать эти связанные сущности в свой запрос?
Ортунд
32

CosisEntitiesКласс СВОЙ DbContext. Когда вы создаете контекст вusing блоке, вы определяете границы для своей операции, ориентированной на данные.

В своем коде вы пытаетесь выдать результат запроса из метода, а затем завершить контекст внутри метода. Операция, которой вы передаете результат, затем пытается получить доступ к объектам, чтобы заполнить представление сетки. Где-то в процессе привязки к сетке осуществляется доступ к свойству с отложенной загрузкой, и Entity Framework пытается выполнить поиск для получения значений. Это не удается, потому что связанный контекст уже закончился.

У вас две проблемы:

  1. При привязке к сетке вы загружаете объекты с ленивой загрузкой. Это означает, что вы выполняете множество отдельных операций запроса к SQL Server, которые все замедлят. Вы можете решить эту проблему, либо настроив связанные свойства с готовностью загружаться по умолчанию, либо попросив Entity Framework включить их в результаты этого запроса с помощью Includeметода расширения.

  2. Вы преждевременно завершаете свой контекст: a DbContextдолжен быть доступен во всей выполняемой единице работы, удаляя его только тогда, когда вы закончите с текущей работой. В случае ASP.NET единицей работы обычно является обрабатываемый HTTP-запрос.

Пол Тернер
источник
Большое спасибо за полезную информацию и хорошее объяснение проблемы. На самом деле я новичок в Entity Framework, а также в Linq, поэтому эта информация действительно является для меня отличным уроком.
барсан
20

Нижняя граница

Ваш код получил данные (объекты) через entity-framework с включенной отложенной загрузкой, и после удаления DbContext ваш код ссылается на свойства (связанные / отношения / объекты навигации), которые не были явно запрошены.

Более конкретно

С InvalidOperationExceptionэтим сообщением всегда означает одно и то же: вы запрашиваете данные (сущности) из entity-framework после удаления DbContext.

Простой случай:

(эти классы будут использоваться для всех примеров в этом ответе, и предполагается, что все свойства навигации были настроены правильно и имеют связанные таблицы в базе данных)

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

Последняя строка выбрасывает InvalidOperationException потому что dbContext не отключил ленивую загрузку, и код обращается к свойству навигации Pet после того, как Context был удален с помощью оператора using.

Отладка

Как вы находите источник этого исключения? Помимо просмотра самого исключения, которое будет выброшено именно в том месте, где оно возникает, применяются общие правила отладки в Visual Studio: установите стратегические точки останова и проверьте свои переменные. , либо наведя указатель мыши на их имена, открыв ( Быстро) Окно просмотра или использование различных панелей отладки, таких как Локальные и Авто.

Если вы хотите узнать, где установлена ​​или не установлена ​​ссылка, щелкните ее имя правой кнопкой мыши и выберите «Найти все ссылки». Затем вы можете разместить точку останова в каждом месте, которое запрашивает данные, и запустить свою программу с подключенным отладчиком. Каждый раз, когда отладчик прерывается в такой точке останова, вам необходимо определить, должно ли быть заполнено ваше свойство навигации или необходимы ли запрашиваемые данные.

Способы избежать

Отключить отложенную загрузку

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

Плюсы: вместо исключения InvalidOperationException свойство будет иметь значение null. Доступ к свойствам null или попытка изменить свойства этого свойства вызовет исключение NullReferenceException .

Как явно запросить объект при необходимости:

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

В предыдущем примере Entity Framework материализует Pet в дополнение к Person. Это может быть выгодно, потому что это единственный вызов базы данных. (Однако также могут быть огромные проблемы с производительностью в зависимости от количества возвращаемых результатов и количества запрошенных свойств навигации, в этом случае не будет никакого снижения производительности, поскольку оба экземпляра представляют собой только одну запись и одно соединение).

или

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

В предыдущем примере Entity Framework материализует Pet независимо от Person, выполняя дополнительный вызов базы данных. По умолчанию Entity Framework отслеживает объекты, полученные из базы данных, и при обнаружении соответствующих свойств навигации автоматически заполняет эти сущности магическим способом. В этом случае , так как PetIdна Personобъект совпадает с Pet.Id, Entity Framework будет назначить Person.PetнаPet значение , извлеченное, прежде чем значение присваивается переменной для домашних животных.

Я всегда рекомендую этот подход, поскольку он заставляет программистов понимать, когда и как код запрашивает данные через Entity Framework. Когда код генерирует исключение с нулевой ссылкой для свойства объекта, вы почти всегда можете быть уверены, что не запрашивали эти данные явно.

Эрик Филипс
источник
13

Это очень поздний ответ, но я решил проблему, отключив ленивую загрузку:

db.Configuration.LazyLoadingEnabled = false;
Рикардо Понтуаль
источник
На мой взгляд, StackOverflow творит чудеса с одним лайнером. И это сделало это для меня, слава вам!
Harold_Finch
Обратной стороной является то, что вам нужно использовать .Include и тому подобное для загрузки свойств навигации.
boylec1986
1

В моем случае я передавал все модели «Пользователи» в столбец, и он не отображался правильно, поэтому я просто передал «Users.Name» и исправил его.

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

Большинство других ответов указывают на нетерпеливую загрузку, но я нашел другое решение.

В моем случае у меня был объект EF InventoryItemс коллекцией InvActivityдочерних объектов.

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

И поскольку я извлекал из коллекции дочерних объектов вместо контекстного запроса (с IQueryable), Include()функция была недоступна для реализации активной загрузки. Поэтому вместо этого я решил создать контекст, в котором я использовал, GetLatestActivity()и attach()возвращаемый объект:

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

Таким образом, вы не застряли в нетерпеливой загрузке.

Zorgarath
источник
По сути, это активная загрузка, вы загрузили объект через контекст. Есть только два варианта; жадная загрузка и отложенная загрузка.
Эрик Филипс
@ErikPhilips, правильно, это ленивая загрузка с новым контекстом данных
Зоргарат
1
@ErikPhilips - также есть явная загрузка - docs.microsoft.com/en-us/ef/ef6/querying/…
Дэйв Блэк,
1

Если вы используете ASP.NET Core и задаетесь вопросом, почему вы получаете это сообщение в одном из методов асинхронного контроллера, убедитесь, что вы возвращаете, Taskа не void- ASP.NET Core удаляет внедренные контексты.

(Я отправляю этот ответ, так как этот вопрос занимает одно из первых мест в результатах поиска по этому сообщению об исключении, и это тонкая проблема - возможно, это полезно для людей, которые ищут его в Google.)

Джон
источник