解决方法“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(); } }
该错误提到了Gridview
的LoanProductName
列。 提到:我正在使用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
块中创建上下文时,您正在为面向数据的操作定义边界。
在你的代码中,你试图从一个方法中发出查询的结果,然后在方法中结束上下文。 您传递结果的操作然后尝试访问实体以填充网格视图。 在绑定到网格的过程中,正在访问延迟加载的属性,实体框架试图执行查找以获取值。 它失败了,因为关联的上下文已经结束了。
你有两个问题:
-
当你绑定到网格时,你正在懒加载实体。 这意味着你正在对SQL Server进行大量的单独的查询操作,这将使所有的操作变慢。 您可以通过默认情况下使相关属性预加载来解决此问题,或者使用
Include
扩展方法要求Entity Framework将其包含在此查询的结果中。 -
你提前结束你的上下文:一个
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.Id
与Pet.Id
相匹配,实体框架将赋值Person.Pet
到Pet
值,然后将值赋给Pet
变量。
我总是推荐这种方法,因为它迫使程序员通过实体框架了解代码是什么时候请求数据。 当代码在实体的属性上抛出空引用异常时,几乎总是可以确定你没有明确地请求这些数据。