Решение "Экземпляр 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 для этого.)