130

我正在尝试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();
    }
}

错误是LoanProductName提到Gridview. 提到:我使用 C#、ASP.net、SQL-Server 2008 作为后端数据库。

我对实体框架很陌生。我不明白为什么我会收到这个错误。任何人都可以帮助我吗?

4

8 回答 8

188

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

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

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

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

于 2013-08-23T08:54:32.643 回答
32

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

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

你有两个问题:

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

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

于 2013-08-23T08:56:45.133 回答
27

底线

您的代码已通过启用延迟加载的实体框架检索数据(实体),并且在处理 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 中的一般调试规则适用:放置战略断点并检查您的变量,方法是将鼠标悬停在它们的名称上,打开一个 ( 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

在前面的示例中,Entity Framework 将物化除了 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 会跟踪它从数据库中检索到的对象,如果找到匹配的导航属性,它将自动神奇地填充这些实体。在这种情况下,由于对象PetId上的Person与 匹配Pet.Id,实体框架将在将值分配给宠物变量之前将 分配给检索Person.Pet到的值。Pet

我总是推荐这种方法,因为它迫使程序员了解代码何时以及如何通过实体框架请求数据。当代码在实体的属性上引发空引用异常时,您几乎可以始终确定您没有明确请求该数据。

于 2017-10-25T17:01:46.287 回答
17

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

db.Configuration.LazyLoadingEnabled = false;
于 2018-05-23T17:33:25.703 回答
1

就我而言,我将所有模型“用户”传递给列,但没有正确映射,所以我只是传递了“用户名称”并修复了它。

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();
于 2017-05-17T14:44:35.410 回答
1

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

就我而言,我有一个带有子对象InventoryItem集合的 EF 对象。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";
}

因此,您不会被急切加载所困。

于 2017-07-13T17:04:14.357 回答
1

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

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

于 2018-11-08T11:10:30.747 回答
-3

可能

 -> 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();
            }
于 2021-03-04T11:57:41.153 回答