Importações de arquivos CSV no .Net

Eu percebo que esta é uma questão de novato, mas eu estou procurando uma solução simples – parece que deveria haver uma.

Qual é a melhor maneira de importar um arquivo CSV para uma estrutura de dados fortemente tipada? Novamente simples = melhor.

Confira a Biblioteca Open Source do FileHelpers .

O TextFieldParser da Microsoft é estável e segue a RFC 4180 para arquivos CSV. Não desanime pelo namespace Microsoft.VisualBasic ; é um componente padrão no .NET Framework, basta adicionar uma referência ao assembly global Microsoft.VisualBasic .

Se você está compilando para Windows (ao contrário do Mono) e não espera ter que analisar arquivos CSV “quebrados” (não compatíveis com RFC), então esta seria a escolha óbvia, já que é livre, irrestrito, estável, e ativamente suportado, a maioria dos quais não pode ser dito para FileHelpers.

Consulte também: Como: Ler de arquivos de texto delimitados por vírgula no Visual Basic para um exemplo de código VB.

Use uma conexão do 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(); 

Se você está esperando cenários bastante complexos para análise de CSV, nem pense em rolar nosso próprio analisador . Existem muitas ferramentas excelentes por aí, como o FileHelpers , ou mesmo as do CodeProject .

O ponto é que este é um problema bastante comum e você pode apostar que muitos desenvolvedores de software já pensaram e resolveram este problema.

Brian dá uma boa solução para convertê-lo em uma coleção fortemente tipada.

A maioria dos methods de análise de CSV fornecidos não leva em consideração campos de escape ou algumas das outras sutilezas de arquivos CSV (como campos de corte). Aqui está o código que eu pessoalmente uso. É um pouco áspero em torno das bordas e praticamente não há relatórios de erros.

 public static IList> Parse(string content) { IList> records = new List>(); StringReader stringReader = new StringReader(content); bool inQoutedString = false; IList record = new List(); 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(); 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; } 

Observe que isso não manipula o caso de borda de campos que não estão sendo delimitados por aspas duplas, mas meerley tendo uma string entre aspas. Veja este post para um pouco de uma melhor expansão, bem como alguns links para algumas bibliotecas adequadas.

Eu concordo com @ NotMyself . FileHelpers é bem testado e lida com todos os tipos de casos de borda que você eventualmente terá que lidar se você fizer isso sozinho. Dê uma olhada no que o FileHelpers faz e apenas escreva o seu, se tiver certeza absoluta de que (1) você nunca precisará lidar com os casos extremos que o FileHelpers faz, ou (2) adora escrever esse tipo de coisa e vai ser muito feliz quando você tem que analisar coisas como esta:

1, “Bill”, “Smith”, “Supervisor”, “No Comment”

2, ‘Drake,’, ‘O’Malley’, “Zelador,

Oops, eu não sou citado e estou em uma nova linha!

Eu estava entediado, então eu modifiquei algumas coisas que escrevi. Ele tenta encapsular a análise em uma maneira OO, reduzindo a quantidade de iterações no arquivo, apenas itera uma vez no topo para cada.

 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 persons = new List(); // 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 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 GetPersons() { foreach (String[] record in this.Parser.GetRecords()) { yield return new Person() { Name = record[0], Address = record[1], DOB = DateTime.Parse(record[2]), }; } } } } 

Há dois artigos no CodeProject que fornecem código para uma solução, um que usa StreamReader e um que importa dados CSV usando o Microsoft Text Driver .

Uma maneira simples de fazer isso é abrir o arquivo e ler cada linha em uma matriz, linked list, estrutura de dados de sua escolha. Tenha cuidado ao lidar com a primeira linha embora.

Isso pode estar acima da sua cabeça, mas parece haver uma maneira direta de acessá-los também usando uma string de conexão .

Por que não tentar usar Python em vez de C # ou VB? Ele tem um bom módulo CSV para importar que faz todo o trabalho pesado para você.

Eu tive que usar um analisador de CSV no .NET para um projeto neste verão e resolvi o Microsoft Jet Text Driver. Você especifica uma pasta usando uma string de conexão e consulta um arquivo usando uma instrução SQL Select. Você pode especificar tipos fortes usando um arquivo schema.ini. Eu não fiz isso no começo, mas então eu estava obtendo resultados ruins onde o tipo de dados não era imediatamente aparente, como números IP ou uma input como “XYQ 3.9 SP1”.

Uma limitação que encontrei é que ele não pode manipular nomes de coluna acima de 64 caracteres; trunca. Isso não deve ser um problema, exceto que eu estava lidando com dados de input muito mal projetados. Retorna um ADO.NET DataSet.

Esta foi a melhor solução que encontrei. Eu ficaria desconfiado de usar meu próprio analisador CSV, já que provavelmente perderia alguns dos casos finais, e não encontrei nenhum outro pacote de análise de CSV gratuito para o .NET.

EDIT: Além disso, só pode haver um arquivo schema.ini por diretório, então eu dinamicamente anexado a ele para digitar fortemente as colunas necessárias. Ele somente digitará com intensidade as colunas especificadas e será inferido para qualquer campo não especificado. Eu realmente gostei disso, pois eu estava lidando com a importação de um arquivo CSV com mais de 70+ e não queria especificar cada coluna, apenas as que não se comportavam bem.

Eu digitei algum código. O resultado no datagridviewer parecia bom. Ele analisa uma única linha de texto para um arrays de objects.

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

Se você puder garantir que não há vírgulas nos dados, a maneira mais simples provavelmente seria usar String.split .

Por exemplo:

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

Pode haver bibliotecas que você poderia usar para ajudar, mas isso provavelmente é o mais simples possível. Apenas certifique-se de não ter vírgulas nos dados, caso contrário, você precisará analisá-las melhor.