2010-09-10 119 views
10

我使用foreach/IQueryable和LINQ-to-SQL遍历一个小型(〜10GB)表。 看起来是这样的:使用foreach对IQueryable进行迭代会导致内存不足异常

using (var conn = new DbEntities() { CommandTimeout = 600*100}) 
{ 
    var dtable = conn.DailyResults.Where(dr => dr.DailyTransactionTypeID == 1); 
    foreach (var dailyResult in dtable) 
    { 
     //Math here, results stored in-memory, but this table is very small. 
     //At the very least compared to stuff I already have in memory. :) 
    } 
} 

Visual Studio调试器在foreach循环的基础抛出一小会儿后,一个不折不扣的内存异常。我假设dtable的行没有被刷新。该怎么办?

+0

那你已经存储在内存中是大于10GB?你的意思是10 MB? – msarchet 2010-09-10 21:01:30

+0

我在这台机器上有16GB的内存,但是至少有一半的内存正在被任何窗口膨胀加上SQL缓存使用。我无法将10GB存储到内存中,所以我用完了。我很惊讶IQueryable检索整个表...我期望它一次获取一行或少量行。 – Gleno 2010-09-12 18:54:19

+0

我似乎已经能够通过将编译目标更改为x64而不是x86,这使得我的机器上使用更多的内存。然而,我在我的foreach循环中迭代的数据并不是很大,所以我认为循环内部的东西没有正确地收集垃圾。 – 2012-08-15 16:55:16

回答

12

IQueryable<DailyResult> dtable将试图枚举时,整个查询结果加载到内存... foreach循环的迭代之前。它不会在foreach循环迭代期间加载一行。如果你想要这种行为,请使用DataReader

+0

现在我已经将表格导出到一个平面文件,并逐行阅读。下次我会像专业人士一样使用DataReader。 :) – Gleno 2010-09-12 18:48:24

6

你打电话〜10GB小吗?你有幽默感!

你可能会考虑加载行块,又名分页。

conn.DailyResults.Where(dr => dr.DailyTransactionTypeID == 1).Skip(x).Take(y); 
+0

除非OP有20 GB RAM,否则这是处理这种情况的唯一方法。 – Justin 2010-09-10 21:33:49

+1

我不确定,你的意思是告诉我这种分页方法是有效的吗?我很惊讶,IQueriable想把东西加载到内存中。我的意思是,为什么不把它作为一种数组来向无助的程序员指出其讨厌的内涵。 :) – Gleno 2010-09-12 18:50:54

2

使用DataReader是一个倒退,除非在LINQ中有使用它的方法。我以为我们试图摆脱ADO。

上面提出的解决方案可行,但它确实很丑。这里是我的代码:

int iTake = 40000; 
int iSkip = 0; 
int iLoop; 
ent.CommandTimeout = 6000; 
while (true) 
{ 
    iLoop = 0; 
    IQueryable<viewClaimsBInfo> iInfo = (from q in ent.viewClaimsBInfo 
             where q.WorkDate >= dtStart && 
             q.WorkDate <= dtEnd 
             orderby q.WorkDate 
             select q) 
             .Skip(iSkip).Take(iTake); 
    foreach (viewClaimsBInfo qInfo in iInfo) 
    { 
    iLoop++; 
    if (lstClerk.Contains(qInfo.Clerk.Substring(0, 3))) 
    { 
      /// Various processing.... 
    } 
    } 
    if (iLoop < iTake) 
    break; 
    iSkip += iTake; 
} 

您可以看到我必须检查记录用完,因为foreach循环将以40,000条记录结束。不好。

2011年6月10日更新:即使这不起作用。在2,000,000个左右的记录中,我收到了内存不足的例外情况。这也令人难以忍受。当我修改它来使用OleDB时,它在大约15秒内(相对于10分钟以上)运行并且没有耗尽内存。有没有人有一个快速运行并运行的LINQ解决方案?

+0

我不确定我是否遵循一些怪异的部分,但这个想法是=>查询,跳过,采取。太棒了,除了现在你得到一个不同的问题的部分 - 需要多少。也欢迎来到stackoverflow! :D – Gleno 2011-06-07 18:54:03

+0

格莱诺,谢谢。我不确定你认为“怪异的部分”,尽管“怪异”似乎是我的中间名。 :)不幸的是,我回到了ADO.Net,如前所述。 – 2011-06-10 16:44:47

1

我会建议使用SQL来修改这些数据。

0

使用.AsNoTracking() - 它告诉DbEntities 不缓存检索的行

using (var conn = new DbEntities() { CommandTimeout = 600*100}) 
{ 
    var dtable = conn.DailyResults 
       .AsNoTracking()  // <<<<<<<<<<<<<< 
       .Where(dr => dr.DailyTransactionTypeID == 1); 
    foreach (var dailyResult in dtable) 
    { 
     //Math here, results stored in-memory, but this table is very small. 
     //At the very least compared to stuff I already have in memory. :) 
    } 
}