为什么处理实体上下文不能释放内存



在我的测试中,我逐行读取文本文件,并插入一个实体和其他相关实体。问题是当插入太多时,我会收到内存不足的异常。

为了防止这种情况,我为每50行创建一个新的DbContext,并处理掉旧的DbContext。我的理解是,这将从早期的实体操作中释放内存,但内存会继续攀升,如果文件足够大,就会发生内存不足异常。这与实体代码有关,就好像我删除了添加实体的代码行一样——内存保持一致的使用。

下面是我的代码的简化版本。

public class TestClass
{
  public void ImportData(byte[] fileBytes)
  {
    using (Stream stream = new MemoryStream(fileBytes))
    {
        TextFieldParser parser = new TextFieldParser(stream);
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");
       while (!parser.EndOfData)
       {
          //Processes 50 lines creates a new DbContext each time its called
          ImportBatch(parser);          
       } 
    }
  }
  public void ImportBatch(TextFieldParser parser)
  {
    using(myDbContext context = new myDbContext())
    {
      context.Configuration.AutoDetectChangesEnabled = false;
      int batchCount = 0;
      while (!parser.EndOfData && batchCount < 50)
      {
         string[] fields = parser.ReadFields();
         //Here I call some code that will add an entity and add releated entities
         //In its navigation properties
         MyService.AddMyEntity(fields,myDbContext);
         batchCount++;
      } 
     myDbContext.ChangeTracker.DetectChanges();
     myDbContext.SaveChanges();
    }
  }
}

当我每50次插入就处理和创建一个新的上下文时,我希望内存使用率保持不变,但在前两千行中似乎是不变的,但在那之后,内存不断攀升,单位为OutOfMemory异常。

以以下方式处理dbContext不会导致内存释放,这是有原因的吗?

EDIT-添加了我的添加实体方法的一些简化代码

public void AddMyEntity(string[] fields, MyDbContext, myDbContext)
{
   MyEntity myEntity = new MyEntity();
   newRequest.InsertDate = DateTime.UtcNow;
   newRequest.AmendDate = DateTime.UtcNow;
   //If I remove this line the memory does not consistently climb
   myDbContext.MyEntities.Add(myEntity);

   foreach(string item in fields)
   {
     ReleatedEntity releatedEntity = new ReleatedEntity();
     releatedEntity.Value = item;
     newRequest.ReleatedEntities.Add(releatedEntity);   
   }
}

另一个编辑

经过更多的测试,这与Glimpse探查器有关。我已经在我的项目中包含了Glimpse,web配置有一个类似于下面的部分。

  <glimpse defaultRuntimePolicy="On" endpointBaseUri="~/Glimpse.axd">
<tabs>
  <ignoredTypes>
    <add type="Glimpse.Mvc.Tab.ModelBinding, Glimpse.Mvc5"/>
    <add type="Glimpse.Mvc.Tab.Metadata, Glimpse.Mvc5"/>
  </ignoredTypes>
</tabs>
<inspectors>
  <ignoredTypes>
    <add type="Glimpse.Mvc.Inspector.ModelBinderInspector, Glimpse.Mvc5"/>
  </ignoredTypes>
</inspectors>

将defaultRuntimePolicy设置为"关闭"修复了内存泄漏问题。仍然不确定为什么

在对象上调用Dispose并不一定会释放内存。当对象不再被任何活动对象引用时,垃圾收集器会从内存中删除这些对象。调用Dispose可能会释放其他资源(例如,在DbContext的情况下,通过关闭打开的SQL连接),但只有当对象不再被引用时,它才会成为垃圾收集的候选者。

不能保证垃圾收集器会在特定的时间点运行。调用Dispose当然不会导致它运行。话虽如此,我很惊讶它在你耗尽记忆之前不会运行。您可以强制它使用GC.Collect运行,但这确实没有必要。

您可能仍然有对上下文对象的引用,这导致它们不符合垃圾收集的条件。例如,您将myDbContext传递给服务层中的AddEntity方法——这是否存储了包含对上下文的反向引用(甚至是间接引用)的内容?

因为无论何时调用ImportBatch(解析器),它都会创建一个新的DbContext。每个50不是1个DbContext。您可以尝试使用get属性进行计数,并将上下文返回给您。类似这样的东西:

int _batchCount = 0;
public myDbContext _db;
public myDbContext Db
{
    get
    {
        // If batchCount > 50 or _db is not created we need to create _db
        if (_db == null || _batchCount > 50)
        {
            // If db is  already created _batchcount > 50
            if (_db != null)
            {
                _db.ChangeTracker.DetectChanges();
                _db.SaveChanges();
                _db.Dispose();
            }
            _db = new myDbContext();
            _db.Configuration.AutoDetectChangesEnabled = false;
            _batchCount = 0;
        }
        batchCount++;
        return _db;
    }
}

此外,在MyService.AddMyEntity(fields);中,您使用的是MyService类中的DbContext,而不是在使用行中创建的DbContext。

最新更新