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

Я пытаюсь заполнить GridView с помощью Entity 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. Упоминается: я использую С#, ASP.net, SQL-Server 2008 в качестве внутренней базы данных.

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

Ответ 1

По умолчанию 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.

Ответ 2

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

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

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

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

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

Ответ 3

Нижняя линия

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

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

InvalidOperationException с этим сообщением всегда означает одно и то же: вы запрашиваете данные (сущности) из структуры сущностей после удаления 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: устанавливайте стратегические контрольные точки и проверяйте свои переменные, наведя указатель мыши на их имена, открывая ( Быстро) Наблюдайте за окном или используя различные панели отладки, такие как Locals и Autos

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

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

Отключить Ленивый-Загрузка

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

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

Ответ 4

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

db.Configuration.LazyLoadingEnabled = false;

Ответ 5

В моем случае я передавал все модели "Пользователи" в столбец, и он неправильно отображался, поэтому я просто передал "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();

Ответ 6

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

В моем случае у меня был объект 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 lazy loading
    // ie   latestAct.lazyLoadedChild.name = "foo";
}

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

Ответ 7

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

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