解决“ObjectContext 实例已被释放,不能再用于需要连接的操作”InvalidOperationException
Posted
技术标签:
【中文标题】解决“ObjectContext 实例已被释放,不能再用于需要连接的操作”InvalidOperationException【英文标题】:Solving "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection" InvalidOperationException 【发布时间】:2013-08-26 05:47:03 【问题描述】:我正在尝试使用 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 作为后端数据库。
我对实体框架很陌生。我不明白为什么我会收到这个错误。谁能帮帮我?
【问题讨论】:
您是否正在访问 gridview 中的任何导航属性。如果这样做,您还需要在查询中包含这些导航表。喜欢query.Include("SomeOtherTable")
尝试创建一个代理类来托管您的实体,或者至少返回一个匿名对象。从我的角度来看,使用 ef 确实需要创建代理类来实现你的逻辑,使用 edmx 就像 db 访问层而不是业务。
是的,在 gridview 中我也得到了另一个表格列。这是 LoanProviderName。
尝试db.MemberLoans.Include("LoanProduct").OrderByDescending()
检查语法,因为我面前没有VS。
您只需要继续包含您在上下文之外访问的所有导航属性,例如db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable)
。检查@Tragedian 和@lazyberezovsky 的答案
【参考方案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 上的Loading Related Entities 文章。
【讨论】:
非常感谢您的有用解释和回答。实际上在这里我包括三个表,所以我不知道如何使用 INCLUDE 添加三个表。你能帮我解决这个问题吗? @barsan 只是一一包含所有导航属性。例如。db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);
将生成 JOIN 查询并立即返回所有数据。
谢谢伙计,完美。我有一个限制延迟加载的 using 语句。很好的答案。
@barsan 防止意外延迟加载的一种简单方法是在DbContext
的构造函数中使用Configuration.LazyLoadingEnabled = false;
将其关闭。延迟加载在某些情况下可能会出现严重的性能问题,因此我通常默认将其关闭。正如 Sergey 所示,这会强制您显式加载任何导航属性。
如果我根本不想在查询中包含这些相关实体怎么办?【参考方案2】:
CosisEntities
类是您的 DbContext
。当您在 using
块中创建上下文时,您正在为面向数据的操作定义边界。
在您的代码中,您尝试从方法发出查询结果,然后在方法中结束上下文。然后将结果传递给的操作尝试访问实体以填充网格视图。在绑定到网格的过程中,某个延迟加载的属性正在被访问,而 Entity Framework 正在尝试执行查找以获取值。它失败了,因为关联的上下文已经结束。
你有两个问题:
当您绑定到网格时,您正在延迟加载实体。这意味着您正在对 SQL Server 执行大量单独的查询操作,这会减慢一切速度。您可以通过默认预先加载相关属性或使用 Include
扩展方法要求 Entity Framework 将它们包含在此查询的结果中来解决此问题。
你过早地结束了你的上下文:DbContext
应该在正在执行的工作单元中可用,只有在你完成手头的工作时才释放它。对于 ASP.NET,一个工作单元通常是正在处理的 HTTP 请求。
【讨论】:
【参考方案3】:在我的情况下,我将所有模型“用户”传递给列,但没有正确映射,所以我只是传递了“用户名称”并修复了它。
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();
【讨论】:
【参考方案4】:大多数其他答案都指向急切加载,但我找到了另一种解决方案。
在我的例子中,我有一个 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";
因此您不会被急切加载所困。
【讨论】:
这基本上是急切加载,您已经通过上下文加载了对象。只有两种选择;急切加载和延迟加载。 @ErikPhilips 对,它是延迟加载新数据上下文 @ErikPhilips - 还有显式加载 - docs.microsoft.com/en-us/ef/ef6/querying/…【参考方案5】:底线
您的代码已通过启用延迟加载的实体框架检索数据(实体),并且在处置 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 没有禁用延迟加载,并且代码在 using 语句释放 Context 后访问 Pet 导航属性。
调试
您如何找到此异常的来源?除了查看异常本身(将在其发生的位置准确抛出)之外,Visual Studio 中的一般调试规则适用:放置战略断点和inspect your variables,或者将鼠标悬停在它们的名称上,打开一个 ( Quick)Watch 窗口或使用各种调试面板,如 Locals 和 Autos。
如果您想找出引用设置或未设置的位置,请右键单击其名称并选择“查找所有引用”。然后,您可以在请求数据的每个位置放置一个断点,并在附加调试器的情况下运行您的程序。每次调试器在此类断点处中断时,您都需要确定您的导航属性是否应该被填充,或者请求的数据是否是必要的。
避免方法
禁用延迟加载
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 之外的 Pet。这可能是有利的,因为它是对数据库的一次调用。 (但是,根据返回结果的数量和请求的导航属性的数量,也可能存在巨大的性能问题,在这种情况下,不会有性能损失,因为两个实例都只有一条记录和一个连接)。
或
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 将通过对数据库进行额外调用,独立于 Person 实现 Pet。默认情况下,Entity Framework 会跟踪它从数据库中检索到的对象,如果它找到匹配的导航属性,它将神奇地自动填充这些实体。在这种情况下,因为Person
对象上的PetId
与Pet.Id
匹配,实体框架将在将值分配给宠物变量之前将Person.Pet
分配给检索到的Pet
值。
我总是推荐这种方法,因为它迫使程序员了解代码何时以及如何通过实体框架请求数据。当代码在实体的属性上引发空引用异常时,您几乎可以始终确定您没有明确请求该数据。
【讨论】:
【参考方案6】:这是一个很晚的答案,但我解决了关闭延迟加载的问题:
db.Configuration.LazyLoadingEnabled = false;
【讨论】:
缺点是你必须使用 .Include 之类的东西来加载导航属性。【参考方案7】:如果您使用 ASP.NET Core 并想知道为什么在您的异步控制器方法中收到此消息,请确保返回 Task
而不是 void
- ASP.NET Core 会处理注入的上下文。
(我发布此答案是因为该问题在该异常消息的搜索结果中居高不下,而且这是一个微妙的问题 - 也许它对使用 Google 搜索它的人有用。)
【讨论】:
【参考方案8】:可能
-> context.Configuration.ProxyCreationEnabled = false;
using (var context = new BDPuntoDeVenta())
context.Configuration.ProxyCreationEnabled = false;
return context.FacturaDetalle.Where(x => x.ID_Factura == _ID_Factura && x.Devuelto != true).ToList();
【讨论】:
另外两个答案直接暗示了这一点。以上是关于解决“ObjectContext 实例已被释放,不能再用于需要连接的操作”InvalidOperationException的主要内容,如果未能解决你的问题,请参考以下文章