Lendo arquivos CSV usando C #


169

Estou escrevendo um aplicativo de importação simples e preciso ler um arquivo CSV, mostrar o resultado em um DataGride mostrar linhas corrompidas do arquivo CSV em outra grade. Por exemplo, mostre as linhas com menos de 5 valores em outra grade. Estou tentando fazer isso assim:

StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
    row = line.Split(',');

    importingData.Add(new Transaction
    {
        Date = DateTime.Parse(row[0]),
        Reference = row[1],
        Description = row[2],
        Amount = decimal.Parse(row[3]),
        Category = (Category)Enum.Parse(typeof(Category), row[4])
    });
}

mas é muito difícil operar em matrizes nesse caso. Existe uma maneira melhor de dividir os valores?


Obrigado pela sua solução. Considere publicá-lo como uma resposta - incluí-lo na pergunta não ajuda na legibilidade.
BartoszKP

Respostas:


363

Não reinvente a roda. Aproveite o que já está no .NET BCL.

  • adicione uma referência ao Microsoft.VisualBasic(sim, ele diz VisualBasic, mas também funciona em C # - lembre-se de que no final tudo é apenas IL)
  • use a Microsoft.VisualBasic.FileIO.TextFieldParserclasse para analisar o arquivo CSV

Aqui está o código de exemplo:

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData) 
    {
        //Processing row
        string[] fields = parser.ReadFields();
        foreach (string field in fields) 
        {
            //TODO: Process field
        }
    }
}

Funciona muito bem para mim em meus projetos de C #.

Aqui estão mais alguns links / informações:


18
Eu realmente gostaria que houvesse uma maneira de não usar bibliotecas VB, mas isso funcionou perfeitamente! Obrigado!
gillonba

5
+1: Acabei de quebrar o leitor Fast CSV da lumenworks em um arquivo de 53Mb. Parece que o cache da linha falhou após 43.000 linhas e embaralhou o buffer. Tentei o VB TextFieldParsere ele fez o truque. Obrigado
Gone Coding

10
+1 Ótima resposta, pois acho que muitas pessoas não sabem que essa classe existe. Uma coisa que os futuros espectadores devem observar é que a configuração parser.TextFieldType = FieldType.Delimited;não é necessária se você ligar parser.SetDelimiters(",");, pois o método define a TextFieldTypepropriedade para você.
Brian

10
Verifique também: dotnetperls.com/textfieldparser . TextFieldParser tem desempenho pior que String.Split e StreamReader. No entanto, há uma grande diferença entre string.Split e TextFieldParser. O TextFieldParser lida com casos estranhos, como uma vírgula em uma coluna: você pode nomear uma coluna como "text with quote"", and comma"e pode obter o valor correto em text with quote", and commavez de valores separados incorretamente. Portanto, você pode optar por String.Split se o CSV for muito simples.
Yongwei Wu

5
Observe que você pode precisar adicionar uma referência ao Microsoft.VisualBasic para usar isso. Clique com o botão direito do mouse no seu projeto no Visual Studio, escolha Adicionar> Referência e marque a caixa Microsoft.VisualBasic.
Derek Kurth

37

Minha experiência é que existem muitos formatos csv diferentes. Especialmente como eles lidam com o escape de aspas e delimitadores dentro de um campo.

Estas são as variantes que encontrei:

  • as aspas são citadas e dobradas (excel), isto é, 15 "-> campo1," 15 "" ", campo3
  • as aspas não são alteradas, a menos que o campo seja citado por algum outro motivo. ou seja, 15 "-> campo1,15", campos3
  • as aspas são escapadas com \. ou seja, 15 "-> campo1," 15 \ "", campo3
  • as cotações não são alteradas (nem sempre é possível analisar corretamente)
  • delimitador é citado (excel). ou seja, a, b -> campo1, "a, b", campo3
  • delimitador é escapado com \. ou seja, a, b -> campo1, a \, b, campo3

Eu tentei muitos dos analisadores csv existentes, mas não há um que possa lidar com as variantes nas quais encontrei. Também é difícil descobrir na documentação quais variantes de escape os analisadores suportam.

Nos meus projetos, agora uso o VB TextFieldParser ou um divisor personalizado.


1
Adore esta resposta para os casos de teste que você forneceu!
Matthew Rodatus

2
O principal problema é que a maioria das implementações não se importa com o RFC 4180, que descreve o formato CSV e como os delimitadores devem ser escapados.
Jenny O'Reilly

A RFC-4180 é de 2005, o que parece antigo agora, mas lembre-se: a estrutura .Net foi lançada em 2001. Além disso, as RFCs nem sempre são padrões oficiais e, nesse caso, não têm o mesmo peso que, digamos , ISO-8601 ou RFC-761.
Joel Coehoorn

23

Eu recomendo o CsvHelper da Nuget .

(Adicionar uma referência ao Microsoft.VisualBasic simplesmente não parece certo, não é apenas feio, provavelmente nem é multiplataforma.)


2
É exatamente tão multiplataforma quanto o C #.
PRMan

errado, o Microsoft.VisualBasic.dll no Linux vem de fontes Mono, que possuem uma implementação diferente da da Microsoft e existem algumas coisas que não são implementadas, por exemplo: stackoverflow.com/questions/6644165/…
knocte

(Além disso, a linguagem VB nunca teve nenhum foco nas empresas envolvidas na criação / desenvolvimento do projeto Mono, por isso está muito atrasada em termos de esforços, em comparação com o ecossistema / ferramentas C #.)
knocte

1
Tendo jogado com os dois, eu acrescentaria que CsvHelpervem com uma linha embutida no mapeador de classes; ele permite variações nos cabeçalhos das colunas (se presentes) e até aparentemente variações na ordem das colunas (embora eu não tenha testado o último eu mesmo). Em suma, parece muito mais "alto nível" do que TextFieldParser.
David

1
Sim, o espaço para nome Microsoft.VisualBasic não está disponível no .NET Core 2.1
N4ppeL

13

Às vezes, o uso de bibliotecas é legal quando você não deseja reinventar a roda, mas, nesse caso, é possível fazer o mesmo trabalho com menos linhas de código e mais fácil de ler, em comparação com o uso de bibliotecas. Aqui está uma abordagem diferente que eu acho muito fácil de usar.

  1. Neste exemplo, eu uso o StreamReader para ler o arquivo
  2. Regex para detectar o delimitador de cada linha (s).
  3. Uma matriz para coletar as colunas do índice 0 para n

using (StreamReader reader = new StreamReader(fileName))
    {
        string line; 

        while ((line = reader.ReadLine()) != null)
        {
            //Define pattern
            Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

            //Separating columns to array
            string[] X = CSVParser.Split(line);

            /* Do something with X */
        }
    }

4
Certamente isso tem problemas com dados que contêm novas linhas?
Doogal

Agora, os arquivos de dados CSV não são conhecidos por conter linhas vazias entre os dados, mas se você tiver uma fonte que faça isso, nesse caso, seria apenas um simples teste de regex para remover espaços em branco ou linhas que não continham nada antes de executar o leitor. verifique aqui para exemplos diferentes: stackoverflow.com/questions/7647716/…
Mana

1
Certamente, uma abordagem baseada em caracteres é mais natural para esse tipo de problema do que uma expressão regular. Dependendo da presença de aspas, o comportamento deve ser diferente.
Casey

6

CSV pode ficar complicado reais rápido.

Use algo robusto e bem testado:
FileHelpers: www.filehelpers.net

O FileHelpers é uma biblioteca .NET gratuita e fácil de usar para importar / exportar dados de comprimento fixo ou registros delimitados em arquivos, strings ou fluxos.


5
Eu acho que o FileHelper está tentando fazer muito de uma só vez. A análise de arquivos é um processo de duas etapas em que você primeiro divide as linhas em campos e, em seguida, analisa os campos em dados. A combinação das funções dificulta o processamento de detalhes como mestre-detalhe e filtragem de linha.
precisa saber é o seguinte


4

Outro desta lista, o Cinchoo ETL - uma biblioteca de código aberto para ler e gravar arquivos CSV

Para um exemplo de arquivo CSV abaixo

Id, Name
1, Tom
2, Mark

Rapidamente você pode carregá-los usando a biblioteca como abaixo

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Se você tiver uma classe POCO correspondente ao arquivo CSV

public class Employee
{
   public int Id { get; set; }
   public string Name { get; set; }
}

Você pode usá-lo para carregar o arquivo CSV como abaixo

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Confira os artigos no CodeProject sobre como usá-lo.

Isenção de responsabilidade: eu sou o autor desta biblioteca


Oi, você pode carregar csv para tabela SQL - eu não sei o cabeçalho na tabela CSV antes da mão. Apenas espelhe o que está no csv para a tabela sql
aggie

Sim você pode. consulte este link stackoverflow.com/questions/20759302/…
RajN

2
private static DataTable ConvertCSVtoDataTable(string strFilePath)
        {
            DataTable dt = new DataTable();
            using (StreamReader sr = new StreamReader(strFilePath))
            {
                string[] headers = sr.ReadLine().Split(',');
                foreach (string header in headers)
                {
                    dt.Columns.Add(header);
                }
                while (!sr.EndOfStream)
                {
                    string[] rows = sr.ReadLine().Split(',');
                    DataRow dr = dt.NewRow();
                    for (int i = 0; i < headers.Length; i++)
                    {
                        dr[i] = rows[i];
                    }
                    dt.Rows.Add(dr);
                }

            }

            return dt;
        }

        private static void WriteToDb(DataTable dt)
        {
            string connectionString =
                "Data Source=localhost;" +
                "Initial Catalog=Northwind;" +
                "Integrated Security=SSPI;";

            using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
                        cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
                        cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";

                        con.Open();
                        cmd.ExecuteNonQuery();
                    }
                }

         }

de onde você copiou esta solução?
precisa saber é o seguinte

0

Antes de tudo, precisamos entender o que é CSV e como escrevê-lo.

  1. Toda próxima string ( /r/n) é a próxima linha da "tabela".
  2. As células "Tabela" são separadas por algum símbolo delimitador. Os símbolos mais frequentemente usados ​​são \tou,
  3. Todas as células podem conter esse símbolo delimitador (a célula deve começar com o símbolo de aspas e terminar com esse símbolo nesse caso)
  4. Cada célula pode conter /r/nsybols (a célula deve começar com o símbolo de aspas e terminar com esse símbolo nesse caso)

A maneira mais fácil de o C # / Visual Basic trabalhar com arquivos CSV é usar a Microsoft.VisualBasicbiblioteca padrão . Você só precisa adicionar a referência necessária e a seguinte string à sua classe:

using Microsoft.VisualBasic.FileIO;

Sim, você pode usá-lo em C #, não se preocupe. Essa biblioteca pode ler arquivos relativamente grandes e suporta todas as regras necessárias, para que você possa trabalhar com todos os arquivos CSV.

Há algum tempo, eu escrevi uma classe simples para leitura / gravação de CSV com base nessa biblioteca. Usando esta classe simples, você poderá trabalhar com CSV como na matriz de 2 dimensões. Você pode encontrar minha turma no seguinte link: https://github.com/ukushu/DataExporter

Exemplo simples de uso:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

0

Para concluir as respostas anteriores, pode ser necessário uma coleção de objetos de seu arquivo CSV, analisados ​​pelo método TextFieldParserou pelo string.Splitmétodo, e depois cada linha é convertida em um objeto via Reflexão. Obviamente, você primeiro precisa definir uma classe que corresponda às linhas do arquivo CSV.

Usei o serializador CSV simples de Michael Kropat encontrado aqui: Classe genérica para CSV (todas as propriedades) e reutilizei seus métodos para obter os campos e propriedades da classe desejada.

Desserializei meu arquivo CSV com o seguinte método:

public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
    if (!File.Exists(fileFullPath))
    {
        return null;
    }

    var list = new List<T>();
    var csvFields = GetAllFieldOfClass<T>();
    var fieldDict = new Dictionary<int, MemberInfo>();

    using (TextFieldParser parser = new TextFieldParser(fileFullPath))
    {
        parser.SetDelimiters(delimiter);

        bool headerParsed = false;

        while (!parser.EndOfData)
        {
            //Processing row
            string[] rowFields = parser.ReadFields();
            if (!headerParsed)
            {
                for (int i = 0; i < rowFields.Length; i++)
                {
                    // First row shall be the header!
                    var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
                    if (csvField != null)
                    {
                        fieldDict.Add(i, csvField);
                    }
                }
                headerParsed = true;
            }
            else
            {
                T newObj = new T();
                for (int i = 0; i < rowFields.Length; i++)
                {
                    var csvFied = fieldDict[i];
                    var record = rowFields[i];

                    if (csvFied is FieldInfo)
                    {
                        ((FieldInfo)csvFied).SetValue(newObj, record);
                    }
                    else if (csvFied is PropertyInfo)
                    {
                        var pi = (PropertyInfo)csvFied;
                        pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
                    }
                    else
                    {
                        throw new Exception("Unhandled case.");
                    }
                }
                if (newObj != null)
                {
                    list.Add(newObj);
                }
            }
        }
    }
    return list;
}

public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
    return
        from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
        let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
        orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
        select mi;            
}

0

Eu sugiro usar o CsvHelper.

Aqui está um exemplo rápido:

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

A documentação completa pode ser encontrada em: https://joshclose.github.io/CsvHelper

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.