实体框架内存未释放

20

我正在使用一个非常简单的asp.net mvc应用程序,其中包括Entity Framework 6.0.2和.Net 4.5.1:

public class HomeController : Controller
{
   public ActionResult Index()
   {
      int count;
      using (var db = new LocalContext())
      {
         count = db.Counters.Count();
      }
      return View(count);
   }
}

public class Counter
{
   public int Id { get; set; }
}

public class LocalContext : DbContext
{
   public DbSet<Counter> Counters { get; set; }
}

如果我对其进行负载测试,最终会出现内存溢出异常。 (tinyget -srv:localhost -port:<port> -uri:/home/index/ -threads:30 -loop:5000)。在性能监视器中,我看到第二代堆稳步增长。如果我使用较小的循环值(比如500),大小会增加直到tinyget停止。然后堆大小保持不变(至少20分钟,之后我停止了服务器)。

我做错了什么?

编辑

所以我尝试了Simon Mouriers的建议并且省略了EF代码。然后我就没有内存问题了。所以我想,也许如果我使用发布版本而不是调试版本,会有所不同。确实如此!一段时间后释放了内存,我可以对站点进行高负载测试。然后我切换回调试模式看看是否可以获得更多信息,结果...即使在调试模式下也再没有问题了。FML,我花了一整天的时间去解决它,现在我无法再现这个问题了。


3
我认为你没有做错什么,很可能这是EF臃肿代码库的问题。 - mxmissile
我也找不到任何问题,只是觉得很奇怪内存使用量没有下降。 - John Landheer
int count = 0; return View(count); 替换代码似乎并没有展示问题,这表明问题出在 EF 上而不是其他地方? - ta.speot.is
1
你可能在EF 6中遇到了一个错误,尝试降级到EF 5。如果你能确认这是EF 5和6之间的改变,我认为微软会很高兴听到你的意见(参考:http://entityframework.codeplex.com/workitem/1605 - 最后的评论)。 - Frode Nilsen
1
看起来你可能会遇到这个问题:http://entityframework.codeplex.com/workitem/1605 - stames
显示剩余7条评论
5个回答

3
在您的情况下,从DbContext继承的内部管理类需要实现IDisposable接口,在LocalContext中添加以下内容:
public void Dispose()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
     if (disposing)
     {
        // Manage any native resources.
     }
   //Handle any other cleanup.
}

如果没有特别覆盖dispose调用,使用语句仅会针对基类调用Dispose()方法,而您需要处理父类和基类。


这是错误的建议。子类没有引入任何需要额外处理的新资源,基类的Dispose实现已经足够了。请参阅https://learn.microsoft.com/en-us/archive/msdn-magazine/2007/july/clr-inside-out-digging-into-idisposable中的“从可处理类型派生”部分。 - Monsignor

1
我看不出你的代码有什么问题。也许这可能是底层ADO.NET提供程序的问题。你使用的是哪个数据库?
我记得我曾经遇到过一些单元测试的问题,没有释放SQLite数据库文件,最终我用这段代码解决了这个问题(在我的DbContext类中)。
public class LocalContext : DbContext
{
    protected override void Dispose(bool disposing)
    {
        var connection = this.Database.Connection;
        base.Dispose(disposing);
        connection.Dispose();
    }
}

也许与此无关,但我想试一试。

0
我会选择创建一个连接到数据库的类..
public class DBconnection : IDisposable
{
    private ChatEntities _db = new ChatEntities();

    protected ChatEntities Db {
        get { return _db; }
    }

    public void Dispose()
    {
        if (_db != null)
        {
            _db.Dispose();
        }
    }
}

然后当您想要连接和操作时,让我们称之为DBlogic类。

public class DBlogic : DBconnection
{
       internal void WriteToDB(String str){
          //Do something ...

          Db.SaveChanges();
        }
}

这最终会导致Dispose清空资源..而且更加简洁..至少对我的眼睛来说:D


你的代码会关闭已打开的数据库连接,除此之外没有其他问题。但是问题在于托管内存的使用。 - Kirill Bestemyanov
是的,我现在可以看到了。。@KirillBestemyanovhttp://msdn.microsoft.com/zh-cn/library/vstudio/b1yfkh5e(v=vs.100).aspx - Jonathan Pick

0

这可能不是正确的答案,但我建议使用IoC容器来管理您的上下文,并使用TrasientScope或PerHttpRequest范围进行添加(由于ioc容器语法的大量变化,未提供示例)。 如果您想要一个具体的例子,请回复您想要的DI。


1
在所示的代码中,上下文是按请求范围进行作用域限定的。这是我能想到的最简单的例子,在这种情况下,DI只会使它更加复杂化。 - John Landheer
1
那么它必须是一个未被释放的资源,因此其父对象没有被收集。GC只收集没有引用的资源。您必须查看仍未释放的内容。我更喜欢使用IoC容器,因为它几乎可以为我完成所有的脏活累活,这就是为什么我向您推荐它的原因。 - Florin V

-2
实际上,在这种情况下,OutOfMemoryException 是正常的,因为垃圾回收器不会在你完成对象后立即发生。在这种情况下,您需要使用 GC.Collect() 对所有代的内存进行收集,并立即回收所有无法访问的内存。
public class HomeController : Controller
{
   public ActionResult Index()
   {
      int count;
      using (var db = new LocalContext())
      {
         count = db.Counters.Count();
      }

      GC.Collect();
      return View(count);
   }
}

请注意,在生产代码中不应使用GC.Collect(),因为它会干扰垃圾回收机制。

2
这最多只是治标不治本。问题可能是Dispose模式问题或其他形式的悬挂引用。它可能是EF中的一个错误,甚至可能是.NET本身出了问题。请注意,OP说在构建Release后切换回Debug时,无法再现该问题。 - Craig Tullis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接