cómo ejecutar varias consultas LINQ en el mismo archivo de datos

Soy nuevo LINQ y actualmente lo estoy usando para procesar grandes conjuntos de datos en formato csv (medio millón de registros). estoy usando un StreamReader para abrir los archivos e implementar el IEnumerable<> interfaz para poblar los resultados. A continuación puede ver la parte principal del código de lectura:

IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
{
    using (StreamReader streamReader = new StreamReader(filename)){
        streamReader.ReadLine();
        while (!streamReader.EndOfStream){
            string[] values = streamReader.ReadLine().Split(new char[] { ',' });
            Person p = new Person();
            p.Name = values[0];
            p.Age = Convert.ToInt16(values[1]);
            p.Score = Convert.ToDouble(values[2]);
            p.PlotArea = Convert.ToInt16(values[3]);
            p.ForecastConsumption = Convert.ToDouble(values[4]);
            p.Postcode = values[5];
            p.PropertyType = values[6];
            p.Bedrooms = Convert.ToInt16(values[7]);
            p.Occupancy = Convert.ToInt16(values[8]);

            yield return p;
        }
    }
}

y aquí hay una consulta típica:

var query = from person in reader
            where person.Score > 36.55 && person.Bedrooms < 3
            select person;

Mi pregunta es esta, cada vez que quiero ejecutar una consulta, el StreamReader tiene que abrir el archivo. ¿Hay alguna forma de que pueda abrir el archivo una vez y ejecutar varias consultas?

Para su información, estoy muy impresionado con LINQ, se tarda 1.2 segundos en ejecutar la consulta anterior. Es solo que ejecutaré muchas reglas para los conjuntos de datos.

preguntado el 22 de mayo de 12 a las 16:05

Si tiene .net4+, puede prescindir del StreamReader por completo y usar File.ReadLines en su lugar. Hace lo mismo (es decir, le permite enumerar un archivo sin cargarlo todo en la memoria). -

reader es de que tipo? Si quieres hacer todo en la memoria, también puedes usar File.ReadAllLines -

Echa un vistazo a esto blogs.msdn.com/b/jmstall/archive/2005/08/06/…, si los archivos son grandes, puede comenzar con él para crear un caché compartido (multiproceso) (solo recuerde la línea/posición actual en una base por subproceso) -

También me encanta LINQ, pero reescriba esa consulta sin LINQ y tomará menos de un segundo (yo diría medio segundo si sacrifica un poco su código). -

3 Respuestas

Mi pregunta es esta, cada vez que quiero ejecutar una consulta, StreamReader tiene que abrir el archivo. ¿Hay alguna forma de que pueda abrir el archivo una vez y ejecutar varias consultas?

Bueno, la forma más sencilla sería cargar todo el archivo en una lista, por ejemplo

var list = reader.ToList();

// Now run multiple queries over list

Obviamente, eso requerirá bastante memoria, pero va a ser el más simple camino a seguir. Si desea unir varias consultas, tendrá que resolver exactamente lo que quiere hacer: el modelo de composición en LINQ es principalmente en términos de encadenamiento operaciones de consulta juntas en lugar de crear múltiples consultas desde la misma fuente.

De lo contrario, si ni la complejidad de "múltiples consultas en un solo paso" ni "cargar todo el archivo en la memoria" funcionan para usted, es probable que tenga que cargarlo varias veces.

Una opción intermedia que puede ser más eficiente en memoria sería leer todos los líneas en la memoria (por lo que solo realiza la actividad del disco una vez) pero luego analizar gramaticalmente esas líneas varias veces. Eso será mucho más eficiente en términos de IO, pero peor en términos de CPU.

contestado el 22 de mayo de 12 a las 16:05

Solo tenga en cuenta que hacer esto implica que todo el contenido del archivo esté en la memoria. Para un archivo grande que puede ser una mala idea o simplemente no es posible. Si el archivo es muy grande, puede quieres para abrir el lector y leer el archivo varias veces. - Servir

En realidad, no es una tarea fácil (si el archivo es lo suficientemente grande). Debe abstraer a su lector usando un objeto de acceso, pero puede ser complicado avanzar y retroceder en el archivo. - Adriano Repetti

Hmmmm, en realidad hay un problema con la memoria, ya que algunos de los otros archivos están llegando a más de un millón de registros. Me gusta la idea de leer todas las líneas de la cadena y hacer el análisis varias veces. - Dimitris

@Dimitris: Sin embargo, eso todavía significa mantener todo el archivo en la memoria. ¿Qué tan grande es el archivo? Tenga en cuenta que las cadenas en .NET son UTF-16 ... por lo que es posible que incluso deba mantener presionada la tecla datos binarios sin procesar en la memoria y convertirlo en texto cada vez (lo cual es bastante similar a usar un archivo mapeado en la memoria). - jon skeet

@JonSkeet: el archivo que estoy usando es solo para algunas pruebas iniciales. El simulador final probablemente usará archivos con 20-50 columnas y registros de 500K a 2000K. - Dimitris

Esto debería funcionar:

  return from line in File.ReadAllLines(filename)
                     let values = line.Split(new char[] { ',' })
                     select new Person{
                Name = values[0];
                Age = Convert.ToInt16(values[1]);
                Score = Convert.ToDouble(values[2]);
                PlotArea = Convert.ToInt16(values[3]);
                ForecastConsumption = Convert.ToDouble(values[4]);
                Postcode = values[5];
                PropertyType = values[6];
                Bedrooms = Convert.ToInt16(values[7]);
                Occupancy = Convert.ToInt16(values[8]);
            };

contestado el 22 de mayo de 12 a las 16:05

Su situación será una compensación de rendimiento entre:

  1. leer todo el archivo en la memoria y ejecutar las consultas que necesite
  2. simplemente iterando sobre el archivo varias veces.

Si es lo último, intente usar File.ReadLines que proporciona una buena interfaz IEnumerable sobre el archivo IO:

public Person ReadPerson(string[] personLine)
{
    Person p = new Person();
    p.Name = personLine[0];
    p.Age = Convert.ToInt16(personLine[1]);
    p.Score = Convert.ToDouble(personLine[2]);
    p.PlotArea = Convert.ToInt16(personLine[3]);
    p.ForecastConsumption = Convert.ToDouble(personLine[4]);
    p.Postcode = personLine[5];
    p.PropertyType = personLine[6];
    p.Bedrooms = Convert.ToInt16(personLine[7]);
    p.Occupancy = Convert.ToInt16(personLine[8]);
}

Y uso:

var file = File.ReadLines("/filepath/")
    .Select(line => ReadPerson(line.Split(',')));

var query = from person in file
    where person.Score > 36.55 && person.Bedrooms < 3
    select person;

contestado el 22 de mayo de 12 a las 16:05

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