解决“ ObjectContext 实例已被释放,不能再用于需要连接的操作”的 InvalidOperationException

我正在尝试使用实体框架填充 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-Server2008作为后端数据库。

我刚接触实体框架。我不明白为什么我会得到这个错误。有人能帮帮我吗?

191597 次浏览

默认情况下,实体框架对导航属性使用延迟加载。这就是为什么这些属性应该标记为 Virtual-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 实例。那是你的问题。在 CosisEntity 使用周围有 using块。在返回实体之前处理上下文。当某些代码稍后尝试使用延迟加载的导航属性时,它会失败,因为此时上下文已被释放。

为了解决这个问题,你可以使用导航属性的即时加载,这是你以后需要的:

IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);

这将预加载所有的成员资格和延迟加载将不会被使用。详细信息见 加载相关实体文章在 MSDN。

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

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

你有两个问题:

  1. 当您绑定到网格时,您是延迟加载实体。这意味着您要对 SQLServer 执行大量单独的查询操作,这将降低所有操作的速度。可以修复这个问题,方法是默认情况下使相关属性渴望加载,或者通过使用 Include扩展方法请求实体框架将它们包含在此查询的结果中。

  2. 您过早地结束了上下文: DbContext应该在所执行的整个工作单元中都可用,只有在您完成手头的工作时才处理它。对于 ASP.NET,工作单元通常是正在处理的 HTTP 请求。

在我的例子中,我将所有模型“ Users”传递给 column,但它没有被正确映射,所以我只传递了“ Users”。名字,然后修好了。

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 被释放之后,从实体框架请求数据(实体)。

一个简单的例子:

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

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

正在调试

如何查找此异常的源?除了查看异常本身(异常会在异常发生的位置抛出) ,Visual Studio 中调试的一般规则适用于: 将策略断点和 检查你的变量放置在异常上方,或者将鼠标悬停在它们的名称上,打开一个(快速)监视窗口,或者使用各种调试面板,如 Locals 和 Auto。

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

避免的方法

禁用懒惰加载

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

在前面的示例中,实体框架将实现除了人之外的宠物。这可能是有利的,因为它是一个单一的调用数据库。(然而,根据返回结果的数量和请求的导航属性的数量,也可能存在巨大的性能问题,在这个例子中,不会有性能损失,因为两个实例只有一个记录和一个连接)。

或者

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

在前面的示例中,实体框架将通过对数据库进行附加调用来实现 Pet,而不依赖于 Person。默认情况下,实体框架跟踪从数据库中检索到的对象,如果找到匹配的导航属性,则 自然而然地将填充这些实体。在这个实例中,由于 Person对象上的 PetIdPet.Id匹配,实体框架将在 Person.Pet被分配给 pet 变量之前,将 Person.Pet分配给检索到的 Pet值。

我总是推荐这种方法,因为它强制程序员理解何时以及如何通过实体框架请求代码数据。当代码对实体的属性引发空引用异常时,几乎总是可以确保没有显式请求该数据。

这是一个非常晚的答案,但我解决了关闭延迟加载的问题:

db.Configuration.LazyLoadingEnabled = false;

如果您正在使用 ASP.NET Core,并且想知道为什么会在异步控制器方法中获得此消息,请确保返回 Task而不是 void-ASP.NET Core 处理注入的上下文。

(我发布这个答案是因为这个问题在异常信息的搜索结果中排名很高,而且这是一个微妙的问题——也许对于那些用谷歌搜索它的人来说是有用的。)

有可能

 -> 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();
}