C# DbContext OutOfMemoryException

C# DbContext OutOfMemoryException,c#,entity-framework,C#,Entity Framework,我有一个DbContext,它的数据集大于2000万条记录,必须转换为不同的数据格式。因此,我将数据读入内存,执行一些任务,然后处理DbContext。代码运行良好,但过了一段时间,我就摆脱了内存异常。我已经能够将它缩小到下面的代码段,在这里我检索了2M条记录,然后释放它们并再次获取它们。第一次检索工作正常,第二次检索抛出异常 // first call runs fine using (var dbContext = new CustomDbContext()) { var list

我有一个DbContext,它的数据集大于2000万条记录,必须转换为不同的数据格式。因此,我将数据读入内存,执行一些任务,然后处理DbContext。代码运行良好,但过了一段时间,我就摆脱了内存异常。我已经能够将它缩小到下面的代码段,在这里我检索了2M条记录,然后释放它们并再次获取它们。第一次检索工作正常,第二次检索抛出异常

// first call runs fine
using (var dbContext = new CustomDbContext())
{
    var list = dbContext.Items.Take(2000000).ToArray();
    foreach (var item in list)
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

// second call throws exception
using (var dbContext = new CustomDbContext())
{
    var list = dbContext.Items.Take(2000000).ToArray();
    foreach (var item in list)
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}
GC是否应该自动释放第一个using块中分配的所有内存,以便第二个块与第一个块一样正常运行


在我的实际代码中,我不是一次检索200万条记录,而是在每次迭代中检索0到30K之间的记录。然而,大约15分钟后,我的内存用完了,尽管所有对象都应该被释放。

我怀疑你遇到了LOH。可能您的对象比threashold大,并且它们正在到达那里,因此默认情况下GC没有帮助

试试这个:

看看你的异常是否消失了

i、 e.在第一部分和第二部分之间添加:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(); 

IEnumerable具有GetEnumerator(),因此,如果您只想阅读以下内容,可以尝试使用此方法来避免.ToArray()或.ToList()不必要:

// first call
using (var dbContext = new CustomDbContext())
{
    foreach (var item in dbContext.Items.Take(2000000))
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

// second call
using (var dbContext = new CustomDbContext())
{
    foreach (var item in dbContext.Items.Take(2000000))
    {
        // perform conversion tasks...
        item.Converted = true;
    }
}

运行GC对您没有帮助,您必须在不同的上下文中运行每个迭代。并处理您的上下文

// ID is your primary key

long startID = 0;
while(true){
    using(var db = new CustomDbContext()){
        var slice = db.Items.Where(x=>x.ID > startID)
                            .OrderBy(x=>x.ID)
                            .Take(1000).ToList();

        // stop if there is nothing to process
        if(!slice.Any())
             break;

        foreach(var item in slice){
            // your logic...
            item.Converted = true;
        }

        startID = slice.Last().ID;
    }
}
如果您想更快地处理这些事情,另一种方法是并行运行切片

替代方法

我建议在100x100中使用分割切片,然后我可以并行处理100个项目的100个切片

您可以随时轻松自定义切片以满足您的速度需求

public IEnumerable<IEnumerable<T>> Slice(IEnumerable<T> src, int size){
     while(src.Any()){
         var s = src.Take(size);
         src = src.Skip(size);
         yield return s;
     }
}

long startID = 0;
while(true){
    using(var db = new CustomDbContext()){
        var src = db.Items.Where(x=>x.ID > startID)
                            .OrderBy(x=>x.ID)
                            .Take(10000).Select(x=>x.ID).ToList();

        // stop if there is nothing to process
        if(!src.Any())
             break;

        Parallel.ForEach(src.Slice(100), slice => {

             using(var sdb = new CustomDbContext()){
                 foreach(var item in sdb.Items.Where(x=> slice.Contains(x.ID)){
                     item.Converted = true;
                 }
             }

        } );

        startID = src.Last();
    }
}
public IEnumerable切片(IEnumerable src,int size){
while(src.Any()){
var s=src.Take(尺寸);
src=src.Skip(大小);
收益率;
}
}
长startID=0;
while(true){
使用(var db=new CustomDbContext()){
var src=db.Items.Where(x=>x.ID>startID)
.OrderBy(x=>x.ID)
.Take(10000).选择(x=>x.ID).ToList();
//如果没有要处理的内容,请停止
如果(!src.Any())
打破
Parallel.ForEach(src.Slice(100),Slice=>{
使用(var sdb=new CustomDbContext()){
foreach(sdb.Items.Where(x=>slice.Contains(x.ID))中的var项){
item.Converted=true;
}
}
} );
startID=src.Last();
}
}

重构后,内存被释放。我不知道为什么,但它可以工作

private static void Debug()
{
    var iteration = 0;
    while(true)
    {
        Console.WriteLine("Iteration {0}", iteration++);
        Convert();
    }
}

private static void Convert()
{
    using (var dbContext = new CustomDbContext(args[0]))
    {
        var list = dbContext.Items.Take(2000000).ToList();
        foreach (var item in list)
        {
            item.Converted = true;
        }
    }
}
当我将Convert()的内容移动到Debug()中的while循环时,抛出OutOfMemoryExceptions

private static void Debug()
{
    var iteration = 0;
    while(true)
    {
        Console.WriteLine("Iteration {0}", iteration++);
        using (var dbContext = new CustomDbContext(args[0]))
        {
            // OutOfMemoryException in second iteration
            var list = dbContext.Items.Take(2000000).ToList(); 
            foreach (var item in list)
            {
                item.Converted = true;
            }
        }
    }
}

如果您将
Take
限制为1M会发生什么?第二次查询是否仍然失败?这很奇怪。好奇这里泄漏了什么。请与我们共享转换代码和
实体代码。另一个问题:如果您调用ToList()而不是ToArray(),会有什么区别吗?这是准确的代码吗?变量
list
应该是本地的,就像你在这里发布的一样。另外,你使用的
using
块将自动处理DbContext,而不是列表,如果
list
不是本地的,这可能是问题所在。@Shimmy,第三次迭代后也会发生同样的情况。@JeffPrince,它没有区别erence。不幸的是,这没有任何效果。我没有发现任何涉及大对象的提示。也许你应该先问OP。没有涉及大对象。实际上,对象非常小。只有几个整数和字符串。