解决“ ObjectContext实例已被处置,并且不能再用于需要连接的操作” InvalidOperationException


122

我试图填充一个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();
    }
}

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

我是实体框架的新手。我不明白为什么会收到此错误。有人可以帮我吗?


1
您是否正在访问gridview中的所有导航属性。如果这样做,则还需要在查询中包括这些导航表。像query.Include("SomeOtherTable")
Nilesh

尝试创建一个代理类来托管您的实体,或者至少返回一个匿名对象。从我的角度来看,使用ef确实需要创建代理类来实现您的逻辑,将edmx用作db访问层,而不是作为业务来使用。
Gonzix

是的,在gridview我也得到另一个表列。这是LoanProviderName。
baran

1
尝试db.MemberLoans.Include("LoanProduct").OrderByDescending()检查语法,因为我面前没有VS。
Nilesh

3
您只需要继续包含要在上下文外部访问的所有导航属性,例如db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable)。检查@Tragedian和@lazyberezovsky的答案
Nilesh

Answers:


174

默认情况下,实体框架使用延迟加载来导航属性。这就是为什么应将这些属性标记为虚拟的原因-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实例。那是你的问题。您对usingCosisEntities的用法有所了解。它在返回实体之前布置上下文。稍后某些代码尝试使用延迟加载的导航属性时,它将失败,因为此时已处理上下文。

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

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

这将预加载所有成员身份,并且不会使用延迟加载。有关详细信息,请参阅MSDN上的“ 加载相关实体 ”。


非常感谢您的有用解释和答复。实际上,我这里包含三个表,所以我不知道如何使用INCLUDE添加三个表。你能帮我这个忙吗?
baran

8
@barsan只包含所有导航属性。例如db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);,将生成JOIN查询并立即返回所有数据。
Sergey Berezovskiy

1
非常感谢lazyberezovsky。我非常感谢你。你救了我近一天。从您的解释中,我正在学习有关实体框架的更多信息。谢谢,我的朋友。
baran

谢谢队友,完美。我有一个using语句,它限制了延迟加载。好答案。
ncbl 2014年

4
如果我根本不想在查询中包括那些相关的实体怎么办?
奥特und

32

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

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

您有两个问题:

  1. 当您绑定到网格时,您正在延迟加载实体。这意味着您要对SQL Server进行许多单独的查询操作,这将减慢一切。您可以通过以下方式解决此问题:要么默认设置相关属性为预先加载,要么使用Include扩展方法要求Entity Framework将其包含在此查询的结果中。

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


非常感谢您提供有用的信息并很好地解释了问题。实际上,我在Entity Framework和Linq中都是一个新手,所以这些信息对我来说确实是一个很好的课程。
baran

20

底线

您的代码已通过启用延迟加载的实体框架检索了数据(实体),并且在处理了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中的常规调试规则也适用:放置战略断点并检查变量,方法是将鼠标悬停在它们的名称上,然后打开(快速)监视窗口或使用各种调试面板(例如本地和自动)。

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

避免的方法

禁用延迟加载

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}

优点:该属性将为null,而不是引发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

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

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


13

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

db.Configuration.LazyLoadingEnabled = false;

对我而言,StackOverflow只需一根衬纸就能创造奇迹。这为我做到了,对您表示敬意!
Harold_Finch

缺点是您必须使用.include和类似的东西来加载导航属性。
boylec1986 '18

1

就我而言,我将所有模型“ Users”传递给列,但映射不正确,因此我只是传递了“ 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

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

就我而言,我有一个InventoryItem带有InvActivity子对象集合的EF 对象。

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

因此,您不会急于加载。


这基本上是渴望加载,您已经通过上下文加载了对象。只有两个选项;渴望加载和延迟加载。
Erik Philips

@ErikPhilips对,它是使用新数据上下文进行的延迟加载
Zorgarath

1
@ErikPhilips-也有明确的加载-docs.microsoft.com/en-us/ef/ef6/querying / ...
Dave Black

1

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

(我正在发布此答案,因为该问题在该异常消息的搜索结果中很高,这是一个微妙的问题-也许对使用Google的人很有用。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.