C# 使用实体框架大容量插入时内存意外增长

C# 使用实体框架大容量插入时内存意外增长,c#,entity-framework-5,C#,Entity Framework 5,我必须处理100万个实体来建立事实。应该有大约相同数量的结果事实(100万) 我遇到的第一个问题是批量插入,实体框架的插入速度很慢。所以我使用了这个模式(SLauma的回答)。现在我可以很快地插入实体,一分钟内插入大约100K个实体 我遇到的另一个问题是缺乏处理所有事情的内存。所以我已经“呼叫”了处理过程。为了避免出现内存不足的异常情况,如果我从我的100万个结果事实中列出一个列表,我会得到这个异常 我的问题是,即使分页,内存也总是在增长,我不明白为什么。每次批处理后,都不会释放内存。我认为这很

我必须处理100万个实体来建立事实。应该有大约相同数量的结果事实(100万)

我遇到的第一个问题是批量插入,实体框架的插入速度很慢。所以我使用了这个模式(SLauma的回答)。现在我可以很快地插入实体,一分钟内插入大约100K个实体

我遇到的另一个问题是缺乏处理所有事情的内存。所以我已经“呼叫”了处理过程。为了避免出现内存不足的异常情况,如果我从我的100万个结果事实中列出一个列表,我会得到这个异常

我的问题是,即使分页,内存也总是在增长,我不明白为什么。每次批处理后,都不会释放内存。我认为这很奇怪,因为我在循环的每次迭代中获取侦察构建事实并将它们存储到数据库中。循环完成后,应立即将其从内存中释放。但看起来并不是因为每次迭代后都不会释放内存

在我继续挖掘之前,你能告诉我你是否发现了什么问题吗?更具体地说,为什么while循环迭代后没有释放内存

static void Main(string[] args)
{
  ReceiptsItemCodeAnalysisContext db = new ReceiptsItemCodeAnalysisContext();

  var recon = db.Recons
    .Where(r => r.Transacs.Where(t => t.ItemCodeDetails.Count > 0).Count() > 0)
    .OrderBy( r => r.ReconNum);

  // used for "paging" the processing
  var processed = 0;
  var total = recon.Count();
  var batchSize = 1000; //100000;
  var batch = 1;
  var skip = 0;
  var doBatch = true;

  while (doBatch)
  { // list to store facts processed during the batch
    List<ReconFact> facts = new List<ReconFact>();
    // get the Recon items to process in this batch put them in a list
    List<Recon> toProcess = recon.Skip(skip).Take(batchSize)
      .Include(r => r.Transacs.Select(t => t.ItemCodeDetails))
      .ToList();
    // to process real fast 
    Parallel.ForEach(toProcess, r =>
    { // processing a recon and adding the facts to the list
      var thisReconFacts = ReconFactGenerator.Generate(r);
      thisReconFacts.ForEach(f => facts.Add(f));
      Console.WriteLine(processed += 1);
    });
    // saving the facts using pattern provided by Slauma
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new System.TimeSpan(0, 15, 0)))
    {
      ReceiptsItemCodeAnalysisContext context = null;
      try
      {
        context = new ReceiptsItemCodeAnalysisContext();
        context.Configuration.AutoDetectChangesEnabled = false;
        int count = 0;

        foreach (var fact in facts.Where(f => f != null))
        {
          count++;
          Console.WriteLine(count);
          context = ContextHelper.AddToContext(context, fact, count, 250, true); //context.AddToContext(context, fact, count, 250, true);
        }
        context.SaveChanges();
      }
      finally
      {
        if (context != null)
          context.Dispose();
      }
      scope.Complete();
    }
    Console.WriteLine("batch {0} finished continuing", batch);
    // continuing the batch
    batch++;
    skip = batchSize * (batch - 1);
    doBatch = skip < total;
    // AFTER THIS facts AND toProcess SHOULD BE RESET 
    // BUT IT LOOKS LIKE THEY ARE NOT OR AT LEAST SOMETHING
    // IS GROWING IN MEMORY 
  }
  Console.WriteLine("Processing is done {} recons processed", processed);
}
static void Main(字符串[]args)
{
ReceiptsItemCodeAnalysisContext db=新的ReceiptsItemCodeAnalysisContext();
var侦察=分贝侦察
.Where(r=>r.Transacs.Where(t=>t.ItemCodeDetails.Count>0).Count()>0)
.OrderBy(r=>r.um);
//用于“分页”处理
var=0;
var total=侦察计数();
var batchSize=1000;//100000;
var批=1;
var-skip=0;
var-doBatch=true;
while(doBatch)
{//用于存储批处理过程中处理的事实的列表
列表事实=新列表();
//获取此批次中要处理的侦察项目并将其放入列表中
List toProcess=recon.Skip(Skip).Take(batchSize)
.Include(r=>r.Transacs.Select(t=>t.ItemCodeDetails))
.ToList();
//快速处理
Parallel.ForEach(toProcess,r=>
{//处理侦察并将事实添加到列表中
var thisReconFacts=ReconFactGenerator.Generate(r);
thisReconFacts.ForEach(f=>facts.Add(f));
Console.WriteLine(已处理+=1);
});
//使用Slauma提供的模式保存事实
使用(TransactionScope范围=新TransactionScope(TransactionScopeOption.Required,new System.TimeSpan(0,15,0)))
{
ReceiptsItemCodeAnalysisContext=null;
尝试
{
context=新的ReceiptsItemCodeAnalysisContext();
context.Configuration.AutoDetectChangesEnabled=false;
整数计数=0;
foreach(事实中的var事实,其中(f=>f!=null))
{
计数++;
控制台写入线(计数);
context=ContextHelper.AddToContext(context,fact,count,250,true);//context.AddToContext(context,fact,count,250,true);
}
SaveChanges();
}
最后
{
if(上下文!=null)
context.Dispose();
}
scope.Complete();
}
WriteLine(“批处理{0}已完成继续”,批处理);
//继续批量生产
批处理++;
跳过=批次大小*(批次-1);
多批次=跳过<总计;
//在此之后,应重置事实和TopProcess
//但看起来他们不是或者至少是什么
//在记忆中成长
}
WriteLine(“处理完成{}重新处理”,已处理);
}
Slauma提供的使用实体框架优化批量插入的方法

class ContextHelper
{
  public static ReceiptsItemCodeAnalysisContext AddToContext(ReceiptsItemCodeAnalysisContext context,
  ReconFact entity, int count, int commitCount, bool recreateContext)
  {
    context.Set<ReconFact>().Add(entity);

    if (count % commitCount == 0)
    {
      context.SaveChanges();
      if (recreateContext)
      {
        context.Dispose();
        context = new ReceiptsItemCodeAnalysisContext();
        context.Configuration.AutoDetectChangesEnabled = false;
      }
    }
    return context;
  }
}
class ContextHelper
{
公共静态ReceiptsItemCodeAnalysisContext AddToContext(ReceiptsItemCodeAnalysisContext,
重新事实实体、整数计数、整数提交计数、布尔重新创建上下文)
{
context.Set().Add(实体);
如果(计数%commitCount==0)
{
SaveChanges();
如果(重新创建上下文)
{
context.Dispose();
context=新的ReceiptsItemCodeAnalysisContext();
context.Configuration.AutoDetectChangesEnabled=false;
}
}
返回上下文;
}
}

由于您在整个跑步过程中都有相同的数据上下文,因此可能是缓存。一般来说,当我遇到这个问题时,我发现最简单的方法是确保每个“批处理”都有自己的datacontext,每次迭代都会超出范围。

尝试告诉对象上下文不要跟踪对象,如下所示:

static void Main(string[] args)
{
    ReceiptsItemCodeAnalysisContext db = new ReceiptsItemCodeAnalysisContext();

    var recon = db.Recons
        .AsNoTracking() // <---- add this
        .Where(r => r.Transacs.Where(t => t.ItemCodeDetails.Count > 0).Count() > 0)
        .OrderBy( r => r.ReconNum);

//...
static void Main(字符串[]args)
{
ReceiptsItemCodeAnalysisContext db=新的ReceiptsItemCodeAnalysisContext();
var侦察=分贝侦察
.AsNoTracking()//r.Transacs.Where(t=>t.ItemCodeDetails.Count>0.Count()>0)
.OrderBy(r=>r.um);
//...

在您拥有的代码中,所有一百万个Recon对象都将累积在内存中,直到对象上下文被释放。

我尝试在每次迭代中创建一个数据上下文,但它没有改变任何东西……甚至是方法顶部的
Recon
db?是的,我尝试将每个上下文实例放在一个using{}语句,但遗憾的是没有更改。顺便说一句,在
Parallel.ForEach
上下文中使用诸如
thisReconFacts.ForEach(f=>facts.Add(f));
之类的语句时要非常小心。
List.Add(T)
不是线程安全的。是的,我知道我应该使用线程安全的集合。我将这样做作为进一步的改进。首先,我必须找到一种使用EF插入大量数据的快速方法。谁说你应该使用线程安全的集合?关于
List facts=(来自toProcess中的recon.aspallel()来自ReconFactGenerator.Generate中的fact)呢(侦察)选择事实)。ToList();
?很好,我没有意识到这种linq方法,谢谢,使用更快的方法