2008-08-05 16 views
97

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

¿Cuál es la mejor manera de importar un archivo CSV en una estructura de datos fuertemente tipada? Otra vez simple = mejor.

+0

Este es un duplicado de http://stackoverflow.com/questions/1103495/is-there-a-proper-way-to-read-csv-files –

+7

Teniendo en cuenta esto se creó un año antes de lo 1103495 Creo que esa pregunta es un duplicado de esta. – MattH

+2

Gracias, Matt. Solo intentaba unirlos, no indicar cuál vino primero. Verá que tengo exactamente el mismo texto en la otra pregunta que apunta a este. ¿Hay una mejor manera de unir dos preguntas? –

Respuesta

48
+1

Lamentablemente, esto es LGPL, que es menos que ideal en un entorno corporativo ... –

+5

@John, ¿por qué dices eso?LGPL no le exige que libere ningún código a menos que modifique la biblioteca. (En cuyo caso tendría sentido enviar un parche de todos modos.) –

+0

+1 Acabo de implementar esto ... impresionante –

2

Una buena manera simple de hacerlo es abrir el archivo y leer cada línea en una matriz, lista vinculada, estructura de datos de su elección. Sin embargo, tenga cuidado con el manejo de la primera línea.

Esto puede estar sobre su cabeza, pero parece que hay una manera directa de acceder a ellos también usando un connection string.

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

+1

No salte a Python desde VB por el bien de un analizador CSV. Hay uno en VB. Aunque extrañamente parece haber sido ignorado en las respuestas a esta pregunta. http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.fileio.textfieldparser.aspx – MarkJ

0

Si puede garantizar que no hay comas en los datos, entonces la manera más simple sería probablemente usar String.split.

Por ejemplo:

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

Puede haber bibliotecas que puede utilizar para ayudar, pero eso es probablemente tan simple como usted puede conseguir. Solo asegúrate de no tener comas en los datos, de lo contrario necesitarás analizarlos mejor.

+0

esto no es una solución óptima – roundcrisis

+0

muy malo en el uso de memoria y muchos sobrecargas. Pequeño debe ser menos gracias a unos pocos kilobytes. ¡Definitivamente no es bueno para una csv de 10mb! – ppumkin

+0

Depende del tamaño de su memoria y del archivo. – tonymiao

6

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

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]), 
       }; 
      } 
     } 
    } 
} 
9

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

La mayoría de los métodos de análisis CSV dados no tienen en cuenta los campos de escape o algunas de las otras sutilezas de los archivos CSV (como los campos de recorte). Aquí está el código que uso personalmente. Es un poco rudo en los bordes y prácticamente no reporta 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 esta no maneja el caso borde de los campos no se deliminated por comillas dobles, pero meerley tener una cadena entre comillas dentro de ella. Ver this post para una mejor expansión así como algunos enlaces a algunas bibliotecas apropiadas.

1

Tuve que utilizar un analizador CSV en .NET para un proyecto este verano y me decidí por el Microsoft Jet Text Driver. Usted especifica una carpeta usando una cadena de conexión, luego consulta un archivo usando una declaración SQL Select. Puede especificar tipos fuertes utilizando un archivo schema.ini. No lo hice al principio, pero luego recibí malos resultados en los que el tipo de datos no era aparente de inmediato, como los números de IP o una entrada como "XYQ 3.9 SP1".

Una de las limitaciones que encontré es que no puede manejar nombres de columnas de más de 64 caracteres; 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 de rodar 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 schema.ini por directorio, por lo que lo anexé dinámicamente para escribir con fuerza las columnas necesarias. Solo escribirá con fuerza las columnas especificadas e inferirá para cualquier campo no especificado. Realmente lo aprecié, ya que estaba tratando de importar un fluido CSV de columna de más de 70 y no quería especificar cada columna, solo las que se comportaban mal.

+0

¿Por qué no el analizador de CSV incorporado en VB.NET? http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.fileio.textfieldparser.aspx – MarkJ

12

Si está esperando escenarios bastante complejos para el análisis de CSV, ni siquiera piensa en rodar nuestro propio analizador. Hay muchas herramientas excelentes, como FileHelpers, o incluso de CodeProject.

El punto es que este es un problema bastante común y podría apostar que mucho de los desarrolladores de software ya han pensado y resuelto este problema.

+0

Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace para referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página vinculada cambia. - [De la crítica] (/ crítica/mensajes de baja calidad/13623136) – techspider

+0

Gracias @techspider Espero que haya notado que esta publicación fue del período beta de StackOverflow: D Dicho esto, las herramientas de CSV actualmente se obtienen mejor de los paquetes de Nuget - así que no estoy seguro si incluso las respuestas de enlace son inmunes a los ciclos de evolución de tecnología de 8 años –

9

Estoy de acuerdo con @NotMyself. FileHelpers está bien probado y maneja todo tipo de casos extremos con los que eventualmente tendrá que lidiar si lo hace usted mismo. Eche un vistazo a lo que FileHelpers hace y solo escriba el suyo si está absolutamente seguro de que (1) nunca necesitará manejar los casos extremos que FileHelpers tiene, o (2) le encanta escribir este tipo de cosas y van a esté muy contento cuando se tiene que analizar cosas como esta:

1, "Bill", "Smith", "supervisor", "No Comment"

2, 'Drake', 'O'Malley', "conserje,

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

21

utilizar 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(); 
+0

Esto requiere acceso al sistema de archivos. Por lo que sé, no hay forma de hacer que OLEDB funcione con las transmisiones en memoria :( – UserControl

+1

@UserControl, por supuesto requiere acceso al sistema de archivos. Me preguntó sobre la importación de un archivo CSV – Kevin

+1

No me quejo. De hecho, ' d preferiría la solución OLEDB sobre el resto, pero me sentí frustrado tantas veces cuando necesité analizar CSV en aplicaciones ASP.NET, así que quería tenerlo en cuenta. – UserControl

72

Microsoft TextFieldParser es estable y sigue a RFC 4180 para archivos CSV. No se deje intimidar por el espacio de nombre Microsoft.VisualBasic; es un componente estándar en .NET Framework, simplemente agregue una referencia al ensamblaje global Microsoft.VisualBasic.

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

Consulte también: How to: Read From Comma-Delimited Text Files in Visual Basic para obtener un ejemplo de código VB.

+2

En realidad, no hay nada específico de VB sobre esta clase aparte de su espacio de nombre con nombre desafortunado. definitivamente elijo esta biblioteca si solo necesito un analizador CSV "simple", porque no hay nada de lo que descargar, distribuir o preocuparse en general. Para ello he editado el fraseo centrado en VB a partir de esta respuesta. – Aaronaught

+0

@Aaronaught I cree que sus ediciones son principalmente una mejora.Aunque ese RFC no es necesariamente autorizado, ya que muchos escritores de CSV no lo cumplen, p. Excel [no siempre usa una coma] (http://office.microsoft.com/en-us/excel-help/import-or-export-text-txt-or-csv-files-HP010099725.aspx#BMchange_the_separator_in_all_. csv_text) en archivos "CSV". ¿Tampoco mi respuesta anterior ya decía que la clase podría usarse desde C#? – MarkJ

+0

El 'TextFieldParser' funcionará para delimitados por tabuladores y otros cruces extraños generados por Excel. Me doy cuenta de que su respuesta anterior no afirmaba que la biblioteca era específica de VB, me pareció que implicaba que realmente * significaba * para VB, y no * pretendía * para ser utilizado de C#, que yo no utilizo. No es el caso, hay algunas clases realmente útiles en MSVB. – Aaronaught

1

He introducido algún código. El resultado en el datagridviewer 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; 
    }