2010-09-10 113 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. :) 
    } 
}