OutOfMemoryException:在Task中使用DbContext

本文关键字:DbContext Task OutOfMemoryException | 更新日期: 2023-09-27 18:06:41

这是我目前正在处理的以下设置:

  • 我得到了一个sql表与~ 1,000.000项,我需要更新
  • 更新发生在一个单独的线程(由一个任务启动)
  • 由于线程中的内存有限,所以我以每批1000项的批量处理列表(当在主线程的测试项目中运行这个/没有任何任务时,没有OOM异常)
  • UpdateList()函数要么更新列表的字段,要么在DbContext
  • 中为这个或其他表创建新记录。
  • Process_Failure()函数中,我为整个列表
  • 提供了一个上下文实例
  • Process_Success()函数中,我将while循环移出上下文

private void Process_Success()
{
    var totalProcessedCounter = 0;
    while( true )
    {
        using( var context = new MyDbContext() )
        {
            var list = context.MyClass.OrderBy( x => x.Id )
                .Skip( totalProcessedCounter ).Take( 1000 )
                .ToList();
            if( !list.Any() )
                break;
            UpdateList( list );
            totalProcessedCounter += list.Count;
        }
    }
}
private void Process_Failure()
{
    var totalProcessedCounter = 0;
    using( var context = new MyDbContext() )
    {
        while( true )
        {
            var list = context.MyClass.OrderBy( x => x.Id )
                .Skip( totalProcessedCounter ).Take( 1000 )
                .ToList(); // throws OutOfMemoryException
            if( !list.Any() )
                break;
            UpdateList( list );
            totalProcessedCounter += list.Count;
        }
    }
}
private void UpdateList( List<MyClass> list )
{
    var doSaveChanges = false;
    list = list.Where( x => SomeFilter( x ) ).ToList();
    using( var context = new MyDbContext() )
    {
        foreach( var item in list )
        {
            ChangeItem( item );
        }
        if( doSaveChanges )
            context.SaveChanges();
    }
}

当我在UpdateList()的嵌套函数调用中创建另一个实例时,获得context以某种方式污染/填充?

OutOfMemoryException:在Task中使用DbContext

您获得异常的原因是因为DbContext缓存了您从DB读取的数据,因此在某些时候,如果您不断向其缓存添加实体,您将获得OutOfMemoryException,您将会爆炸您的内存。实体不会被GC清除,因为它们正在被DbContext引用。

尝试使用.AsNoTracking():

private void Process_NoTracking()
{
    var totalProcessedCounter = 0;
    using( var context = new MyDbContext() )
    {
        while( true )
        {
            var list = context
                          .MyClass
                          .AsNoTracking()
                          .OrderBy( x => x.Id )
                          .Skip( totalProcessedCounter )
                          .Take( 1000 )
                          .ToList(); 
            if( !list.Any() )
                break;
            UpdateList( list );
            totalProcessedCounter += list.Count;
        }
    }
}

但是如果你不跟踪实体,更新它们就更困难了(阅读"将现有实体附加到上下文"),因为这些实体不属于任何上下文,它们没有被跟踪。

我不会用EF做这样的事情,这看起来像是一个很好的UPDATE/SELECT SQL语句的任务。