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

bar*_*san 113 c# asp.net entity-framework

我试图填充GridView使用Entity Frameworkm但每次我收到以下错误:

"对象'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();
    }
}
Run Code Online (Sandbox Code Playgroud)

错误是提到的LoanProductNameGridview.提到:我使用C#,ASP.net,SQL-Server 2008作为后端数据库.

我是Entity Framework的新手.我无法理解为什么我会收到此错误.有人可以帮我吗?

Ser*_*kiy 156

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

public class MemberLoan
{
   public string LoandProviderCode { get; set; }
   public virtual Membership Membership { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

实体框架将返回从此实体继承的代理,并向此代理提供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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,实体具有用于加载实体的DbContext实例.那是你的问题.你有using阻止CosisEntities使用.在返回实体之前处理上下文.当某些代码稍后尝试使用延迟加载的导航属性时,它会失败,因为在那一刻处置了上下文.

要修复此行为,您可以使用以后需要的急切加载导航属性:

IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);
Run Code Online (Sandbox Code Playgroud)

这将预加载所有成员资格,并且不会使用延迟加载.有关详细信息,请参阅MSDN上的加载相关实体文章.

  • @barsan只是逐个包含所有导航属性.例如`db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);`将生成JOIN查询并立即返回所有数据. (8认同)
  • 如果我根本不想在查询中包含这些相关实体怎么办? (4认同)

Pau*_*ner 28

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

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

你有两个问题:

  1. 绑定到网格时,您将加载延迟实体.这意味着您正在对SQL Server执行大量单独的查询操作,这会降低所有内容的速度.您可以通过默认情况下急切加载相关属性,或者要求Entity Framework使用Include扩展方法将它们包含在此查询的结果中来解决此问题.

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


Eri*_*ips 17

底线

您的代码已经通过实现框架检索了数据(实体)并启用了延迟加载,并且在处理完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);
Run Code Online (Sandbox Code Playgroud)

最后一行将抛出,InvalidOperationException因为dbContext没有禁用延迟加载,并且代码正在使用using语句处理Context后访问Pet导航属性.

调试

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

如果要查找引用的位置或未设置引用,请右键单击其名称并选择"查找所有引用".然后,您可以在请求数据的每个位置放置断点,并在附加调试器的情况下运行程序.每次调试器在这样的断点上中断时,您需要确定是否应该填充导航属性或者是否需要所请求的数据.

避免的方法

禁用延迟加载

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}
Run Code Online (Sandbox Code Playgroud)

优点:不是抛出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
Run Code Online (Sandbox Code Playgroud)

在前面的示例中,除了Person之外,Entity Framework还将实现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
Run Code Online (Sandbox Code Playgroud)

在前面的示例中,实体框架将通过对数据库进行额外调用来独立于Person实现Pet.默认情况下,Entity Framework会跟踪它从数据库中检索到的对象,如果找到与之匹配的导航属性,则会自动填充这些实体.在这种情况下,因为PetId在上Person对象相匹配的Pet.Id,实体框架将分配Person.PetPet检索到的值,之前的值被分配给宠物变量.

我总是推荐这种方法,因为它迫使程序员通过Entity Framework了解代码何时以及如何处理请求数据.当代码在实体的属性上抛出空引用异常时,您几乎总能确保没有显式请求该数据.


Ric*_*ual 8

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

db.Configuration.LazyLoadingEnabled = false;
Run Code Online (Sandbox Code Playgroud)