Lista genérica de C# para cada OutofMemoryException

Tengo un programa que lee aproximadamente 2 millones de filas de una base de datos en una Lista. Cada fila es una ubicación que contiene información como coordenadas geográficas.

Una vez que se agregan los datos a la Lista, uso un bucle foreach y tomo las coordenadas para crear un archivo kml. El bucle encuentra un error OutOfMemoryException cuando el número de filas es grande (pero funciona perfectamente en caso contrario).

¿Alguna sugerencia sobre cómo manejar esto para que el programa pueda trabajar con conjuntos de datos muy grandes? La biblioteca kml es SharpKML.

Todavía soy nuevo en C #, ¡así que tómalo con calma!

Este es el bucle:

            using (SqlConnection conn = new SqlConnection(connstring))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(select, conn);

            using (cmd)
            {
                SqlDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    double lat = reader.GetDouble(1);
                    double lon = reader.GetDouble(2);
                    string country = reader.GetString(3);
                    string county = reader.GetString(4);
                    double TIV = reader.GetDouble(5);
                    double cnpshare = reader.GetDouble(6);
                    double locshare = reader.GetDouble(7);

                    //Add results to list
                    results.Add(new data(lat, lon, country, county, TIV, cnpshare, locshare));
                }
                reader.Close();
            }
            conn.Close();
        }

            int count = results.Count();
            Console.WriteLine("number of rows in results = " + count.ToString());

            //This code segment generates the kml point plot

            Document doc = new Document();
            try
            {
                foreach (data l in results)
                {
                    Point point = new Point();
                    point.Coordinate = new Vector(l.lat, l.lon);

                    Placemark placemark = new Placemark();
                    placemark.Geometry = point;
                    placemark.Name = Convert.ToString(l.tiv);

                    doc.AddFeature(placemark);

                }
            }
            catch(OutOfMemoryException e)
            {
                throw e;
            }

Esta es la clase utilizada en la Lista

        public class data
    {
        public double lat { get; set; }
        public double lon { get; set; }
        public string country { get; set; }
        public string county { get; set; }
        public double tiv { get; set; }
        public double cnpshare { get; set; }
        public double locshare { get; set; }

        public data(double lat, double lon, string country, string county, double tiv, double cnpshare,
            double locshare)
        {
            this.lat = lat;
            this.lon = lon;
            this.country = country;
            this.county = county;
            this.tiv = tiv;
            this.cnpshare = cnpshare;
            this.locshare = locshare;
        }

    }

preguntado el 12 de junio de 12 a las 17:06

¿Por qué diablos necesitas los 2 millones de filas en la memoria? -

4 Respuestas

¿Por qué necesita almacenar todos los datos antes de escribirlos? En lugar de agregar cada fila a una lista, debe procesar cada fila a medida que se lee y luego olvidarse de ella.

Por ejemplo, intente juntar su código de esta manera:

Document doc = new Document();
while (reader.Read())
{
    // read from db
    double lat = reader.GetDouble(1);
    double lon = reader.GetDouble(2);
    string country = reader.GetString(3);
    string county = reader.GetString(4);
    double TIV = reader.GetDouble(5);
    double cnpshare = reader.GetDouble(6);
    double locshare = reader.GetDouble(7);

    var currentData = new data(lat, lon, country, county, TIV, cnpshare, locshare));

    // write to file
    Point point = new Point();
    point.Coordinate = new Vector(currentData.lat, currentData.lon);

    Placemark placemark = new Placemark();
    placemark.Geometry = point;
    placemark.Name = Convert.ToString(currentData.tiv);

    doc.AddFeature(placemark);
}

Esto solo funcionará si Document Sin embargo, se implementa con sensatez.

Respondido el 12 de junio de 12 a las 17:06

Bien, gran idea. Voy a darle una oportunidad. - Richard Todd

Oliver tiene razón (voto positivo de mi parte). En cuanto al rendimiento, puedes hacer otras cosas. Primero, no consulte los campos que no va a utilizar. Luego mueva todas las declaraciones de variables (código de Oliver) antes de la instrucción while (?). Finalmente, en lugar de esperar a que su servidor sql recopile y envíe todos los registros, hágalo progresivamente con pasos. Por ejemplo, si sus registros tienen un UID y el orden para obtenerlos es este UID, comience con una variable local de C# "var lastID = 0", cambie su declaración de selección a algo como (preformato) "seleccione los 1000 principales... where UID > lastID" y repita sus consultas hasta que no obtenga nada o nada menos de 1000 registros.

Respondido el 12 de junio de 12 a las 17:06

Si no hay una gran demora en completar la lista con datos de la base de datos y no mencionó problemas al completar la lista con datos, ¿por qué no crea inmediatamente su objeto Point and Placemark? El código está debajo.

    var doc = new Document();

    using (SqlConnection conn = new SqlConnection(connstring))
    {
        conn.Open();
        SqlCommand cmd = new SqlCommand(select, conn);

        using (cmd)
        {
            var reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                double lat = reader.GetDouble(1);
                double lon = reader.GetDouble(2);
                string country = reader.GetString(3);
                string county = reader.GetString(4);
                double TIV = reader.GetDouble(5);
                double cnpshare = reader.GetDouble(6);
                double locshare = reader.GetDouble(7);

                var point = new Point();
                point.Coordinate = new Vector(lat , lon );

                var placemark = new Placemark();
                placemark.Geometry = point;
                placemark.Name = Convert.ToString(TIV);

                doc.AddFeature(placemark);

            reader.Close();
        }
        conn.Close();
    }

Si no hay una buena razón para recuperar tantos datos en la memoria, intente con algún enfoque de carga diferida.

Respondido el 12 de junio de 12 a las 17:06

Gracias por las increíbles sugerencias. Idealmente, necesito los otros campos en la consulta, ya que en algún momento se usarán para etiquetar los puntos KML o realizar otras operaciones KML, como el polígono. Pasaré algunas horas esta noche repasando las sugerencias aquí e informaré. Gracias a todos. - Richard Todd

Probé esto. El lector parece funcionar como si estuviera transmitiendo. Sin embargo, ahora recibo una excepción OutOfMemoryException cuando intento escribir el archivo KML. Este será un archivo KML extremadamente grande (por ejemplo, 50 MB), pero aún así no debería ser suficiente para la excepción OutOfMemoryException. ¿Hay otra forma de implementar de manera más eficiente algún tipo de transmisión? Quizás necesite dividir los archivos y luego unirme más tarde. - Richard Todd

¿Realmente necesitas un archivo KML tan grande? Puedes probar con la solución @drdigit stackoverflow.com/a/11001300/1433917, agregue 1000 filas al archivo KML existente en cada iteración. Sin embargo, desde mi conocimiento de GIS, el archivo KML con 50 MB de tamaño posiblemente será lento para procesar por Google Maps o Google Earth, ¿por qué no dividirlo por alguna regla en varios archivos KML más pequeños? Disminuirá la ejecución de su consulta. - Minja

@drdigit,

Evitaría ejecutar consultas en bucle. Una consulta siempre debe devolver tantos datos como se necesiten en ese momento. En este caso, tendría 1000 consultas que devuelven 1000 filas. Tal vez sea mejor para mostrar rápidamente las primeras 1000 filas, pero no estoy seguro de si será más rápido si ejecuta 1000 consultas más rápidas en bucle en lugar de ejecutar solo una consulta. Tal vez me equivoque ...

Creo que su enfoque es bueno para la carga diferida si es necesario en esta situación.

Respondido el 12 de junio de 12 a las 18:06

El número 1000 es solo un ejemplo. En la mayoría de los casos (si los índices de db son los adecuados para las consultas) la diferencia de rendimiento es notable siempre que las consultas se ejecuten bajo la misma conexión. Un factor que puede afectar el rendimiento es la latencia de la red, pero se puede equilibrar (hasta cierto punto) con el número de viajes de ida y vuelta que se basa en el número de muestra 1000. De todos modos, en este caso parece que hablamos de un entorno de host local, lo que significa que probablemente no haya latencia de red. - usuario1088520

UPS. Mis disculpas por no ver su primera respuesta a tiempo. Es correcto y exactamente en el mismo camino que el de Olivers. Entonces, un voto positivo para usted, ya que esta no es una competencia por la "máquina" de escritura más rápida. - usuario1088520

No soy tan bueno con DB, ya que estoy principalmente orientado a la programación y no tuve una situación como esta para probar problemas de rendimiento. Mi respuesta apuntó a una buena (debe tener) práctica de programación: nunca ponga una consulta dentro del ciclo. Pero, si hablamos de buenas prácticas de programación, consultar 2 millones de filas seguramente no es lo indicado. - Minja

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.