2010-09-10 10 views
10

Estoy iterando a través de una tabla pequeña (~ 10 GB) con foreach/IQueryable y LINQ-to-SQL. es como la siguiente:Iterar a través de IQueryable con foreach produce una excepción de falta de memoria

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. :) 
    } 
} 

El depurador de Visual Studio lanza un fuera de excepción de memoria después de un corto periodo de tiempo en la base del bucle foreach. Supongo que las filas de dtable no están sonrojadas. ¿Qué hacer?

+0

¿qué es lo que ya ha almacenado en la memoria que es mayor de 10 GB? ¿Querías decir 10 MB? – msarchet

+0

Tengo 16 GB de memoria en esta máquina, pero al menos la mitad está en uso con cualquier combinación de Windows y SQL. No podía guardar 10 GB en la memoria, así que me quedé sin él. Me sorprende que IQueryable recupere toda la tabla ... Esperaría que obtuviera una o un pequeño número de filas a la vez. – Gleno

+0

Parece que he podido evitar esto cambiando el objetivo de compilación a x64 en lugar de x86, lo que hace uso de más memoria en mi máquina. Sin embargo, los datos en los que estoy iterando en mi ciclo foreach no son enormes, así que creo que las cosas dentro del ciclo no están recogiendo basura correctamente. –

Respuesta

12

La diabla IQueryable<DailyResult> intentará cargar todo el resultado de la consulta en la memoria cuando se enumeran ... antes de que cualquier iteraciones del bucle foreach. No carga una fila durante la iteración del ciclo foreach. Si quieres ese comportamiento, usa DataReader.

+0

Por ahora he exportado la tabla a un archivo plano y lo leo línea por línea. La próxima vez usaré DataReader como un profesional. :) – Gleno

6

¿Llamas ~ 10GB pequeño? ¡Tienes un buen sentido del humor!

Puede considerar cargar filas en fragmentos, también conocidos como paginación.

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

A menos que el OP tenga 20 GB de RAM, esta es la única forma de lidiar con la situación. – Justin

+1

No estoy seguro, ¿quieres decirme que este método de paginación es eficiente? Estoy tan sorprendido de que IQueriable quiera cargar cosas en la memoria. Quiero decir, ¿por qué no hacerlo una variedad de algún tipo para indicar al programador indefenso acerca de sus desagradables intenciones? :) – Gleno

2

Usar DataReader es un paso atrás a menos que haya una forma de usarlo dentro de LINQ. Pensé que estábamos tratando de alejarnos de ADO.

La solución sugerida anteriormente funciona, pero es realmente fea. Aquí está mi código:

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; 
} 

Se puede ver que tengo que comprobar por haber quedado sin registros porque el bucle foreach terminará a 40.000 registros. No está bien.

Actualizado el 10/06/2011: Incluso esto no funciona. Con 2,000,000 de registros más o menos, recibo una excepción de falta de memoria. También es insoportablemente lento. Cuando lo modifiqué para usar OleDB, funcionó en aproximadamente 15 segundos (en lugar de más de 10 minutos) y no se quedó sin memoria. ¿Alguien tiene una solución LINQ que funciona y funciona rápidamente?

+0

No estoy seguro de seguir algunas de las partes más extrañas, pero la idea es => Query, Skip, Take.Fantástico, excepto por la parte donde ahora obtienes un problema diferente: cuánto tomar. ¡También bienvenido a stackoverflow! : D – Gleno

+0

Gleno, gracias. No estoy seguro de lo que considera las "partes más extrañas", aunque "extraño" parece ser mi segundo nombre. :) Volví a ADO.Net, desafortunadamente, como noté. –

1

Sugeriría utilizar SQL en su lugar para modificar estos datos.

0

Uso .AsNoTracking() - Narra DbEntities que no hagan caché filas recuperadas

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. :) 
    } 
} 
Cuestiones relacionadas