Pergunta

Sei que esta é uma pergunta para iniciantes, mas 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.

Foi útil?

Outras dicas

da Microsoft TextFieldParser é estável e segue RFC 4180 para arquivos CSV.Não se deixe abater pelo Microsoft.VisualBasic espaço para nome;é um componente padrão no .NET Framework, basta adicionar uma referência ao global Microsoft.VisualBasic conjunto.

Se você estiver compilando para Windows (em oposição ao Mono) e não prevê ter que analisar arquivos CSV "quebrados" (não compatíveis com RFC), então esta seria a escolha óbvia, pois é gratuita, irrestrita, estável, e com suporte ativo, a maioria dos quais não pode ser dito sobre FileHelpers.

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

Use uma conexão 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ê espera cenários bastante complexos para análise de CSV, nem pense em lançar nosso próprio analisador.Existem muitas ferramentas excelentes por aí, como Ajudantes de arquivos, ou mesmo aqueles de CódigoProjeto.

A questão é que este é um problema bastante comum e você pode apostar que bastante dos desenvolvedores de software já pensaram e resolveram esse problema.

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

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

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

Observe que isso não lida com o caso extremo de campos não serem delimitados por aspas duplas, mas sim com uma string entre aspas dentro deles.Ver esta postagem para uma expansão um pouco melhor, bem como alguns links para algumas bibliotecas adequadas.

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

1,"Bill","Smith","Supervisor", "Sem comentários"

2, 'Drake', 'O'Malley',"Zelador,

Ops, não estou citado e estou em nova linha!

Eu estava entediado, então modifiquei algumas coisas que escrevi.Ele tenta encapsular a análise de maneira OO enquanto reduz a quantidade de iterações no arquivo, ele itera apenas uma vez no topo do foreach.

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

Existem dois artigos no CodeProject que fornecem código para uma solução, um que usa StreamReader e um que importa dados CSV usando o Driver de texto da Microsoft.

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

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

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

Tive que usar um analisador CSV em .NET para um projeto neste verão e optei pelo Microsoft Jet Text Driver.Você especifica uma pasta usando uma cadeia de conexão e, em seguida, consulta um arquivo usando uma instrução SQL Select.Você pode especificar tipos fortes usando um arquivo schema.ini.Não fiz isso no início, mas depois obtive resultados ruins onde o tipo de dados não era imediatamente aparente, como números IP ou uma entrada como "XYQ 3.9 SP1".

Uma limitação que encontrei é que ele não pode lidar com nomes de colunas acima de 64 caracteres;ele trunca.Isso não deveria ser um problema, exceto que eu estava lidando com dados de entrada muito mal projetados.Ele retorna um DataSet ADO.NET.

Esta foi a melhor solução que encontrei.Eu teria cuidado ao lançar meu próprio analisador CSV, já que provavelmente perderia alguns dos casos finais e não encontrei nenhum outro pacote gratuito de análise CSV para .NET por aí.

EDITAR:Além disso, só pode haver um arquivo schema.ini por diretório, então anexei-o dinamicamente para digitar fortemente as colunas necessárias.Ele digitará fortemente apenas as colunas especificadas e inferirá qualquer campo não especificado.Eu realmente gostei disso, pois estava lidando com a importação de um CSV fluido com mais de 70 colunas e não queria especificar cada coluna, apenas as que se comportavam mal.

Eu digitei algum código.O resultado no datagridviewer parecia bom.Ele analisa uma única linha de texto em uma 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;
    }

Se você puder garantir que não há vírgulas nos dados, então 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ê pode usar para ajudar, mas provavelmente é o mais simples possível.Apenas certifique-se de que não pode haver vírgulas nos dados, caso contrário você precisará analisá-los melhor.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top