首页 文章

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

提问于
浏览
92

我试图使用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的新手 . 我无法理解为什么我会收到此错误 . 有人可以帮我吗?

7 回答

  • 26

    底线

    您的代码已经通过实现框架检索了数据(实体)并启用了延迟加载,并且在处理完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中调试的一般规则适用:放置战略断点和inspect your variables,将鼠标悬停在其名称上,打开(快速)观察窗口或使用各种调试面板,如本地和汽车 .

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

    避免的方法

    禁用延迟加载

    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
    

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

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

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

  • 12

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

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

    你有两个问题:

    • 你're lazy-loading entities when you bind to the grid. This means that you'对SQL Server进行了大量单独的查询操作,这将减慢一切 . 您可以通过在默认情况下急切加载相关属性,或者要求Entity Framework使用Include扩展方法将它们包含在此查询的结果中来解决此问题 .

    • 你是过早地结束您的背景:在整个工作单元中应该可以使用 DbContext ,只有在您完成手头的工作时才能处理它 . 对于ASP.NET,工作单元通常是正在处理的HTTP请求 .

  • 1

    在我的情况下,我将所有模型'用户'传递给列并且没有正确映射,所以我只是通过了'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();
    
  • 1

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

    (我发布这个答案,因为这个问题在搜索结果中对于该异常消息很高,这是一个微妙的问题 - 也许这对谷歌的人来说很有用 . )

  • 138

    默认情况下,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实例 . 那是你的问题 . 你有关于CosisEntities使用的阻止 . 在返回实体之前处理上下文 . 当某些代码稍后尝试使用延迟加载的导航属性时,它会失败,因为当时正在处理上下文 .

    要解决此问题,您可以使用以后需要的急切加载导航属性:

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

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

  • 4

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

    db.Configuration.LazyLoadingEnabled = false;
    
  • 0

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

    在我的情况下,我有一个EF对象 InventoryItemInvActivity 子对象的集合 .

    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";
    }
    

    因此,您不会急于加载 .

相关问题