解决方法“ObjectContext实例已经被处置,不能再用于需要连接的操作”InvalidOperationException

我正在尝试使用Entity Frameworkm填充一个GridView但每当我得到以下错误:

对象'COSIS_DAL.MemberLoan'上的属性访问器'LoanProduct'抛出以下异常: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(); } } 

该错误提到了GridviewLoanProductName列。 提到:我正在使用C#,ASP.net,SQL-Server 2008作为后端数据库。

我对Entity Framework相当陌生。 我不明白为什么我得到这个错误。 任何人都可以帮我吗?

默认情况下,实体框架使用延迟加载导航属性。 这就是为什么这些属性应该被标记为虚拟的 – EF为您的实体创建代理类,并覆盖导航属性以允许延迟加载。 例如,如果你有这个实体:

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

实体框架将返回从这个实体继承的代理,并提供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上的加载相关实体文章。

CosisEntities类是你的DbContext 。 在using块中创建上下文时,您正在为面向数据的操作定义边界。

在你的代码中,你试图从一个方法中发出查询的结果,然后在方法中结束上下文。 您传递结果的操作然后尝试访问实体以填充网格视图。 在绑定到网格的过程中,正在访问延迟加载的属性,实体框架试图执行查找以获取值。 它失败了,因为关联的上下文已经结束了。

你有两个问题:

  1. 当你绑定到网格时,你正在懒加载实体。 这意味着你正在对SQL Server进行大量的单独的查询操作,这将使所有的操作变慢。 您可以通过默认情况下使相关属性预加载来解决此问题,或者使用Include扩展方法要求Entity Framework将其包含在此查询的结果中。

  2. 你提前结束你的上下文:一个DbContext应该在整个执行的工作单元中可用,只有当你完成了手头的工作后才能处理它。 在ASP.NET的情况下,一个工作单元通常是正在处理的HTTP请求。

在我的情况下,我传递所有模型的用户列和它没有正确映射,所以我只是通过“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(); 

大多数其他答案指向急切的加载,但我找到了另一个解决方案。

在我的情况下,我有一个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"; } 

因此,您不会急于加载。

底线

你的代码已经通过实体框架检索到数据(实体),延迟加载被启用,并且在DbContext被处置之后,你的代码正在引用没有明确请求的属性(相关/关系/导航实体)。

进一步来说

带有这个消息的InvalidOperationException总是意味着同样的事情:在DbContext被处置之后,你正在从entity-framework请求数据(实体)。

一个简单的例子:

(这些类将用于此答案中的所有示例,并假定所有导航属性都已正确配置,并在数据库中具有关联的表)

 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没有禁用延迟加载,代码在using语句处理Context之后正在访问Pet导航属性。

调试

你如何找到这个异常的来源? 除了查看异常本身,这些异常本身将会发生在它发生的位置,Visual Studio中调试的一般规则也适用:放置战略断点并检查变量 ,方法是将鼠标悬停在其名称上,打开(快速)观察窗口或使用各种调试面板,如本地和汽车。

如果您想查找引用的位置或未设置,请右键单击其名称并选择“查找所有引用”。 然后,您可以在请求数据的每个位置放置一个断点,然后使用附加的调试器运行程序。 每当调试器断开这样的断点时,您需要确定您的导航属性是否应该填充,或者如果所需的数据是必需的。

避免的方法

禁用延迟加载

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

优点:而不是抛出InvalidOperationException该属性将为空。 访问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 

在前面的例子中,除了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 

在前面的例子中,实体框架通过额外的调用数据库来独立于Person实现Pet。 默认情况下,实体框架跟踪它从数据库中检索到的对象,如果它发现匹配它的导航属性将自动奇迹般地填充这些实体。 在这个实例中,因为Person对象上的Pet.IdPet.Id相匹配,实体框架将赋值Person.PetPet值,然后将值赋给Pet变量。

我总是推荐这种方法,因为它迫使程序员通过实体框架了解代码是什么时候请求数据。 当代码在实体的属性上抛出空引用异常时,几乎总是可以确定你没有明确地请求这些数据。