Pregunta

Me doy cuenta de que esta es una pregunta para principiantes, pero estoy buscando una solución simple; parece que debería haber una.

¿Cuál es la mejor manera de importar un archivo CSV a una estructura de datos fuertemente tipada?De nuevo simple = mejor.

¿Fue útil?

Otros consejos

Microsoft Analizador de campos de texto es estable y sigue RFC 4180 para archivos CSV.No te dejes desanimar por el Microsoft.VisualBasic espacio de nombres;es un componente estándar en .NET Framework, solo agregue una referencia al global Microsoft.VisualBasic asamblea.

Si está compilando para Windows (a diferencia de Mono) y no prevé tener que analizar archivos CSV "rotos" (no compatibles con RFC), entonces esta sería la opción obvia, ya que es gratis, sin restricciones, estable, y apoyado activamente, la mayoría de los cuales no se pueden decir de FileHelpers.

Ver también: Cómo:Leer desde archivos de texto delimitados por comas en Visual Basic para un ejemplo de código VB.

Utilice una conexión OleDB.

String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();

Si espera escenarios bastante complejos para el análisis de CSV, Ni siquiera se nos ocurra implementar nuestro propio analizador..Existen muchas herramientas excelentes, como Ayudantes de archivos, o incluso algunos de Proyecto de código.

El punto es que este es un problema bastante común y puedes apostar que mucho de los desarrolladores de software ya han pensado y solucionado este problema.

Brian ofrece una buena solución para convertirla en una colección fuertemente tipada.

La mayoría de los métodos de análisis CSV proporcionados no tienen en cuenta los campos de escape ni algunas de las otras sutilezas de los archivos CSV (como recortar campos).Aquí está el código que uso personalmente.Es un poco tosco y prácticamente no informa de errores.

public static IList<IList<string>> Parse(string content)
{
    IList<IList<string>> records = new List<IList<string>>();

    StringReader stringReader = new StringReader(content);

    bool inQoutedString = false;
    IList<string> record = new List<string>();
    StringBuilder fieldBuilder = new StringBuilder();
    while (stringReader.Peek() != -1)
    {
        char readChar = (char)stringReader.Read();

        if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
        {
            // If it's a \r\n combo consume the \n part and throw it away.
            if (readChar == '\r')
            {
                stringReader.Read();
            }

            if (inQoutedString)
            {
                if (readChar == '\r')
                {
                    fieldBuilder.Append('\r');
                }
                fieldBuilder.Append('\n');
            }
            else
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();

                records.Add(record);
                record = new List<string>();

                inQoutedString = false;
            }
        }
        else if (fieldBuilder.Length == 0 && !inQoutedString)
        {
            if (char.IsWhiteSpace(readChar))
            {
                // Ignore leading whitespace
            }
            else if (readChar == '"')
            {
                inQoutedString = true;
            }
            else if (readChar == ',')
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();
            }
            else
            {
                fieldBuilder.Append(readChar);
            }
        }
        else if (readChar == ',')
        {
            if (inQoutedString)
            {
                fieldBuilder.Append(',');
            }
            else
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();
            }
        }
        else if (readChar == '"')
        {
            if (inQoutedString)
            {
                if (stringReader.Peek() == '"')
                {
                    stringReader.Read();
                    fieldBuilder.Append('"');
                }
                else
                {
                    inQoutedString = false;
                }
            }
            else
            {
                fieldBuilder.Append(readChar);
            }
        }
        else
        {
            fieldBuilder.Append(readChar);
        }
    }
    record.Add(fieldBuilder.ToString().TrimEnd());
    records.Add(record);

    return records;
}

Tenga en cuenta que esto no maneja el caso límite de campos que no están delimitados por comillas dobles, sino que simplemente tienen una cadena entre comillas dentro de ellos.Ver esta publicación para una mejor expansión, así como algunos enlaces a algunas bibliotecas adecuadas.

Estoy de acuerdo con @No a mí mismo. Ayudantes de archivos está bien probado y maneja todo tipo de casos extremos con los que eventualmente tendrás que lidiar si lo haces tú mismo.Eche un vistazo a lo que hace FileHelpers y solo escriba el suyo propio si está absolutamente seguro de que (1) nunca necesitará manejar los casos extremos que hace FileHelpers, o (2) le encanta escribir este tipo de cosas y va a hacerlo. Alégrate mucho cuando tengas que analizar cosas como esta:

1,"Bill","Smith","Supervisor", "Sin comentarios"

2, 'Drake', 'O'Malley', "Conserje,

¡Ups, no estoy citado y estoy en una nueva línea!

Estaba aburrido así que modifiqué algunas cosas que escribí.Intenta encapsular el análisis de manera OO mientras reduce la cantidad de iteraciones a través del archivo, solo itera una vez en la parte superior de cada uno.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

namespace ConsoleApplication1
{
    class Program
    {

        static void Main(string[] args)
        {

            // usage:

            // note this wont run as getting streams is not Implemented

            // but will get you started

            CSVFileParser fileParser = new CSVFileParser();

            // TO Do:  configure fileparser

            PersonParser personParser = new PersonParser(fileParser);

            List<Person> persons = new List<Person>();
            // if the file is large and there is a good way to limit
            // without having to reparse the whole file you can use a 
            // linq query if you desire
            foreach (Person person in personParser.GetPersons())
            {
                persons.Add(person);
            }

            // now we have a list of Person objects
        }
    }

    public abstract  class CSVParser 
    {

        protected String[] deliniators = { "," };

        protected internal IEnumerable<String[]> GetRecords()
        {

            Stream stream = GetStream();
            StreamReader reader = new StreamReader(stream);

            String[] aRecord;
            while (!reader.EndOfStream)
            {
                  aRecord = reader.ReadLine().Split(deliniators,
                   StringSplitOptions.None);

                yield return aRecord;
            }

        }

        protected abstract Stream GetStream(); 

    }

    public class CSVFileParser : CSVParser
    {
        // to do: add logic to get a stream from a file

        protected override Stream GetStream()
        {
            throw new NotImplementedException();
        } 
    }

    public class CSVWebParser : CSVParser
    {
        // to do: add logic to get a stream from a web request

        protected override Stream GetStream()
        {
            throw new NotImplementedException();
        }
    }

    public class Person
    {
        public String Name { get; set; }
        public String Address { get; set; }
        public DateTime DOB { get; set; }
    }

    public class PersonParser 
    {

        public PersonParser(CSVParser parser)
        {
            this.Parser = parser;
        }

        public CSVParser Parser { get; set; }

        public  IEnumerable<Person> GetPersons()
        {
            foreach (String[] record in this.Parser.GetRecords())
            {
                yield return new Person()
                {
                    Name = record[0],
                    Address = record[1],
                    DOB = DateTime.Parse(record[2]),
                };
            }
        }
    }
}

Hay dos artículos sobre CodeProject que proporcionan código para una solución, uno que utiliza Lector de corriente y uno que importa datos CSV utilizando el Controlador de texto de Microsoft.

Una buena forma sencilla de hacerlo es abrir el archivo y leer cada línea en una matriz, lista vinculada o estructura de datos de su elección.Sin embargo, tenga cuidado al manejar la primera línea.

Puede que esto se le pase por alto, pero parece haber una forma directa de acceder a ellos también mediante un Cadena de conexión.

¿Por qué no intentar utilizar Python en lugar de C# o VB?Tiene un bonito módulo CSV para importar que hace todo el trabajo pesado por usted.

Tuve que usar un analizador CSV en .NET para un proyecto este verano y me decidí por el controlador Microsoft Jet Text.Usted especifica una carpeta usando una cadena de conexión y luego consulta un archivo usando una instrucción SQL Select.Puede especificar tipos seguros utilizando un archivo esquema.ini.No hice esto al principio, pero luego obtuve malos resultados donde el tipo de datos no era evidente de inmediato, como números de IP o una entrada como "XYQ 3.9 SP1".

Una limitación con la que me encontré es que no puede manejar nombres de columnas de más de 64 caracteres;se trunca.Esto no debería ser un problema, excepto que estaba tratando con datos de entrada muy mal diseñados.Devuelve un conjunto de datos ADO.NET.

Esta fue la mejor solución que encontré.Sería cauteloso a la hora de implementar mi propio analizador CSV, ya que probablemente me perdería algunos de los casos finales y no encontré ningún otro paquete de análisis CSV gratuito para .NET.

EDITAR:Además, solo puede haber un archivo esquema.ini por directorio, por lo que lo agregué dinámicamente para escribir firmemente las columnas necesarias.Solo escribirá de forma estricta las columnas especificadas e inferirá cualquier campo no especificado.Realmente aprecié esto, ya que estaba importando un CSV fluido de más de 70 columnas y no quería especificar cada columna, solo las que se comportaban mal.

Escribí un código.El resultado en el visor de datos se veía bien.Analiza una sola línea de texto en una lista de objetos.

    enum quotestatus
    {
        none,
        firstquote,
        secondquote
    }
    public static System.Collections.ArrayList Parse(string line,string delimiter)
    {        
        System.Collections.ArrayList ar = new System.Collections.ArrayList();
        StringBuilder field = new StringBuilder();
        quotestatus status = quotestatus.none;
        foreach (char ch in line.ToCharArray())
        {                                
            string chOmsch = "char";
            if (ch == Convert.ToChar(delimiter))
            {
                if (status== quotestatus.firstquote)
                {
                    chOmsch = "char";
                }                         
                else
                {
                    chOmsch = "delimiter";                    
                }                    
            }

            if (ch == Convert.ToChar(34))
            {
                chOmsch = "quotes";           
                if (status == quotestatus.firstquote)
                {
                    status = quotestatus.secondquote;
                }
                if (status == quotestatus.none )
                {
                    status = quotestatus.firstquote;
                }
            }

            switch (chOmsch)
            {
                case "char":
                    field.Append(ch);
                    break;
                case "delimiter":                        
                    ar.Add(field.ToString());
                    field.Clear();
                    break;
                case "quotes":
                    if (status==quotestatus.firstquote)
                    {
                        field.Clear();                            
                    }
                    if (status== quotestatus.secondquote)
                    {                                                                           
                            status =quotestatus.none;                                
                    }                    
                    break;
            }
        }
        if (field.Length != 0)            
        {
            ar.Add(field.ToString());                
        }           
        return ar;
    }

Si puede garantizar que no hay comas en los datos, entonces la forma más sencilla probablemente sería utilizar cadena.split.

Por ejemplo:

String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);

Es posible que haya bibliotecas que pueda utilizar como ayuda, pero probablemente sea lo más simple posible.Solo asegúrese de no poder tener comas en los datos; de lo contrario, deberá analizarlos mejor.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top