Como ler um arquivo CSV em um arquivo de dados .NET

170

Como posso carregar um arquivo CSV em um System.Data.DataTable, criando a tabela de dados com base no arquivo CSV?

A funcionalidade regular do ADO.net permite isso?

Ronnie Overby
fonte
21
Como isso é possivelmente "fora de tópico"? É uma questão específica e 100 pessoas achar que é útil
Ryan
10
@Ryan: Em verdade vos digo ... Os moderadores do StackOverflow são uma espécie de víboras. Fique atrás de mim, moderadores do StackOverflow!
Ronnie Overby

Respostas:

89

Aqui está uma excelente classe que copiará dados CSV em uma tabela de dados usando a estrutura dos dados para criar o DataTable:

Um analisador genérico portátil e eficiente para arquivos simples

É fácil de configurar e fácil de usar. Peço que você dê uma olhada.

Jay Riggs
fonte
Excelente mesmo. Funcionou perfeitamente para mim imediatamente, sem sequer ler a documentação.
Smirkingman 29/05
Isso funcionará em arquivos CSV, onde cada linha pode ter uma estrutura diferente? Eu tenho um arquivo de log com diferentes tipos de eventos registrados que precisariam ser separados em várias tabelas.
gonzobrains
2
@gonzobrains - Provavelmente não; a suposição básica de um arquivo CSV é uma estrutura de dados retangular com base em um único conjunto de cabeçalhos de coluna especificados na primeira linha. O que você tem parece ser mais dados genéricos, separados por vírgula e discriminados, exigindo "ETL" mais sofisticado para analisar do arquivo em instâncias de objetos de vários tipos (que podem incluir DataRows de diferentes DataTables).
Keiths
93

Eu tenho usado OleDbprovedor. No entanto, há problemas se você estiver lendo em linhas que possuem valores numéricos, mas deseja que eles sejam tratados como texto. No entanto, você pode solucionar esse problema criando um schema.iniarquivo. Aqui está o meu método que eu usei:

// using System.Data;
// using System.Data.OleDb;
// using System.Globalization;
// using System.IO;

static DataTable GetDataTableFromCsv(string path, bool isFirstRowHeader)
{
    string header = isFirstRowHeader ? "Yes" : "No";

    string pathOnly = Path.GetDirectoryName(path);
    string fileName = Path.GetFileName(path);

    string sql = @"SELECT * FROM [" + fileName + "]";

    using(OleDbConnection connection = new OleDbConnection(
              @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathOnly + 
              ";Extended Properties=\"Text;HDR=" + header + "\""))
    using(OleDbCommand command = new OleDbCommand(sql, connection))
    using(OleDbDataAdapter adapter = new OleDbDataAdapter(command))
    {
        DataTable dataTable = new DataTable();
        dataTable.Locale = CultureInfo.CurrentCulture;
        adapter.Fill(dataTable);
        return dataTable;
    }
}
Jim Scott
fonte
Obrigado parceiro. Isso me ajudou. Eu tinha um arquivo CSV no qual vírgulas não eram apenas separadoras, elas estavam em todos os lugares dentro de muitos valores de colunas; portanto, criar um regex que dividisse a linha era meio desafiador. O OleDbProvider inferiu o esquema corretamente.
Galilyou
A implementação faz sentido, mas como lidamos com células que contêm tipos de dados mistos. Por exemplo, 40C e etc.?
GKED
GKED, se os dados em que você está lendo sempre tiver um conjunto esperado de colunas e tipos, você poderá colocar na mesma pasta um arquivo shema.ini que informe ao provedor OleDb informações sobre as colunas. Aqui está um link para um artigo da Microsoft que fornece detalhes de como estruturar o arquivo. msdn.microsoft.com/en-us/library/…
Jim Scott
4
Embora essa resposta funcione, eu desaconselharia fortemente. Você introduz uma dependência externa que pode entrar em conflito com outras instalações do office na mesma máquina (use o Excel no seu ambiente de desenvolvimento local?), Dependendo das versões instaladas. Existem pacotes NuGet por aí (ExcelDataReader, CsvHelper) que fazem isso de maneiras mais eficientes e portáteis.
A. Murray
1
@ A.Murray - O que exatamente você quer dizer? Isso usa o provedor OleDb interno no System.Data.dll. Você não precisa instalar nenhum "driver" adicional. E eu ficaria chocado hoje em dia se alguma instalação do Windows não tivesse o driver Jet básico instalado. Este é 1990 CSV ....
Paul Páscoa
40

Decidi usar o Csv Reader de Sebastien Lorion .

A sugestão de Jay Riggs também é uma ótima solução, mas eu simplesmente não precisava de todos os recursos que o Analisador Genérico de Andrew Rissing fornece.

ATUALIZAÇÃO 10/25/2010

Depois de usar o Csv Reader de Sebastien Lorion em meu projeto por quase um ano e meio, descobri que ele lança exceções ao analisar alguns arquivos csv que acredito serem bem formados.

Então, mudei para o Generic Parser de Andrew Rissing e parece estar indo muito melhor.

ATUALIZAÇÃO 22/09/2014

Hoje em dia, uso principalmente esse método de extensão para ler texto delimitado:

https://github.com/Core-Techs/Common/blob/master/CoreTechs.Common/Text/DelimitedTextExtensions.cs#L22

https://www.nuget.org/packages/CoreTechs.Common/

ATUALIZAÇÃO 20/02/2015

Exemplo:

var csv = @"Name, Age
Ronnie, 30
Mark, 40
Ace, 50";

TextReader reader = new StringReader(csv);
var table = new DataTable();
using(var it = reader.ReadCsvWithHeader().GetEnumerator())
{

    if (!it.MoveNext()) return;

    foreach (var k in it.Current.Keys)
        table.Columns.Add(k);

    do
    {
        var row = table.NewRow();
        foreach (var k in it.Current.Keys)
            row[k] = it.Current[k];
    
        table.Rows.Add(row);
    
    } while (it.MoveNext());
}
Ronnie Overby
fonte
Concordo que o leitor de CSV de Sebastien Lorien é ótimo. Eu o uso para processamento pesado de CSV, mas também usei o Andrew's Rissing para pequenos trabalhos e isso me serviu bem. Diverta-se!
21419 Jay Riggs #
Como posso usar essas classes para carregar o CSV no DATATABLE?
Muflix
Eu tentei isso, mas a coleção it.Current.Keys retorna com "System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]" em vez do nome da coluna. Alguma ideia do por quê?
user3658298
Você pode usar delimitadores de vários caracteres?
rola
Não, mas pensei em permitir isso.
precisa saber é o seguinte
32

Ei, está trabalhando 100%

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

Imagem CSV insira a descrição da imagem aqui

Tabela de dados Importada insira a descrição da imagem aqui

Shivam Srivastava
fonte
7
Somente quando 100% das entradas são os arquivos CSV mais simples (o que pode ser verdade no seu caso).
Ronnie Overby
Você está certo. você deve usar o codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll). Tentei seu funcionamento bem.
Shivam Srivastava 01/01
1
Veja minha resposta de 2009.
Ronnie Overby
1
@ShivamSrivastava eu estou recebendo o erro na última linha você está aí, em seguida, dar-lhe outras informações de contato
Sunil Acharya
Embora eu não tenha usado essa versão exatamente, foi com base nela que resolvi meu problema. Obrigado. Funciona muito bem.
Nrod 23/11
13

Sempre usamos o driver Jet.OLEDB, até começarmos a acessar aplicativos de 64 bits. A Microsoft não lançou e não lançará um driver Jet de 64 bits. Aqui está uma solução simples que criamos que usa File.ReadAllLines e String.Split para ler e analisar o arquivo CSV e carregar manualmente um DataTable. Como observado acima, ele NÃO lida com a situação em que um dos valores da coluna contém uma vírgula. Usamos isso principalmente para ler arquivos de configuração personalizados - a parte boa sobre o uso de arquivos CSV é que podemos editá-los no Excel.

string CSVFilePathName = @"C:\test.csv";
string[] Lines = File.ReadAllLines(CSVFilePathName);
string[] Fields;
Fields = Lines[0].Split(new char[] { ',' });
int Cols = Fields.GetLength(0);
DataTable dt = new DataTable();
//1st row must be column names; force lower case to ensure matching later on.
for (int i = 0; i < Cols; i++)
    dt.Columns.Add(Fields[i].ToLower(), typeof(string));
DataRow Row;
for (int i = 1; i < Lines.GetLength(0); i++)
{
    Fields = Lines[i].Split(new char[] { ',' });
    Row = dt.NewRow();
    for (int f = 0; f < Cols; f++)
        Row[f] = Fields[f];
    dt.Rows.Add(Row);
}
Chuck Bevitt
fonte
8

este é o código que eu o uso, mas seus aplicativos devem ser executados com a versão 3.5 da rede

private void txtRead_Click(object sender, EventArgs e)
        {
           // var filename = @"d:\shiptest.txt";

            openFileDialog1.InitialDirectory = "d:\\";
            openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            DialogResult result = openFileDialog1.ShowDialog();
            if (result == DialogResult.OK)
            {
                if (openFileDialog1.FileName != "")
                {
                    var reader = ReadAsLines(openFileDialog1.FileName);

                    var data = new DataTable();

                    //this assume the first record is filled with the column names
                    var headers = reader.First().Split(',');
                    foreach (var header in headers)
                    {
                        data.Columns.Add(header);
                    }

                    var records = reader.Skip(1);
                    foreach (var record in records)
                    {
                        data.Rows.Add(record.Split(','));
                    }

                    dgList.DataSource = data;
                }
            }
        }

        static IEnumerable<string> ReadAsLines(string filename)
        {
            using (StreamReader reader = new StreamReader(filename))
                while (!reader.EndOfStream)
                    yield return reader.ReadLine();
        }
Thomas
fonte
Isso é basicamente o que eu queria apresentar.
Captain Kenpachi
8

Você pode conseguir isso usando a DLL Microsoft.VisualBasic.FileIO.TextFieldParser em C #

static void Main()
        {
            string csv_file_path=@"C:\Users\Administrator\Desktop\test.csv";

            DataTable csvData = GetDataTabletFromCSVFile(csv_file_path);

            Console.WriteLine("Rows count:" + csvData.Rows.Count);

            Console.ReadLine();
        }


private static DataTable GetDataTabletFromCSVFile(string csv_file_path)
        {
            DataTable csvData = new DataTable();

            try
            {

            using(TextFieldParser csvReader = new TextFieldParser(csv_file_path))
                {
                    csvReader.SetDelimiters(new string[] { "," });
                    csvReader.HasFieldsEnclosedInQuotes = true;
                    string[] colFields = csvReader.ReadFields();
                    foreach (string column in colFields)
                    {
                        DataColumn datecolumn = new DataColumn(column);
                        datecolumn.AllowDBNull = true;
                        csvData.Columns.Add(datecolumn);
                    }

                    while (!csvReader.EndOfData)
                    {
                        string[] fieldData = csvReader.ReadFields();
                        //Making empty value as null
                        for (int i = 0; i < fieldData.Length; i++)
                        {
                            if (fieldData[i] == "")
                            {
                                fieldData[i] = null;
                            }
                        }
                        csvData.Rows.Add(fieldData);
                    }
                }
            }
            catch (Exception ex)
            {
            }
            return csvData;
        }
kombsh
fonte
Por favor, não tente reinventar a roda com processamento CSV. Existem muitas alternativas excelentes de código aberto por aí que são muito robustas.
Mike Cole
1
Obrigado Brad, dica útil em relação ao TextFieldParser por manipular cotações incorporadas.
mattpm
3
public class Csv
{
    public static DataTable DataSetGet(string filename, string separatorChar, out List<string> errors)
    {
        errors = new List<string>();
        var table = new DataTable("StringLocalization");
        using (var sr = new StreamReader(filename, Encoding.Default))
        {
            string line;
            var i = 0;
            while (sr.Peek() >= 0)
            {
                try
                {
                    line = sr.ReadLine();
                    if (string.IsNullOrEmpty(line)) continue;
                    var values = line.Split(new[] {separatorChar}, StringSplitOptions.None);
                    var row = table.NewRow();
                    for (var colNum = 0; colNum < values.Length; colNum++)
                    {
                        var value = values[colNum];
                        if (i == 0)
                        {
                            table.Columns.Add(value, typeof (String));
                        }
                        else
                        {
                            row[table.Columns[colNum]] = value;
                        }
                    }
                    if (i != 0) table.Rows.Add(row);
                }
                catch(Exception ex)
                {
                    errors.Add(ex.Message);
                }
                i++;
            }
        }
        return table;
    }
}
Nodir
fonte
3

Me deparei com esse pedaço de código que usa Linq e regex para analisar um arquivo CSV. O artigo de referência agora tem mais de um ano e meio, mas não encontrou uma maneira mais clara de analisar um CSV usando Linq (e regex) além disso. A ressalva é que o regex aplicado aqui é para arquivos delimitados por vírgula (detectará vírgulas entre aspas!) E que pode não ser bom para os cabeçalhos, mas existe uma maneira de superá-los. Dê um pico:

Dim lines As String() = System.IO.File.ReadAllLines(strCustomerFile)
Dim pattern As String = ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
Dim r As System.Text.RegularExpressions.Regex = New System.Text.RegularExpressions.Regex(pattern)
Dim custs = From line In lines _
            Let data = r.Split(line) _
                Select New With {.custnmbr = data(0), _
                                 .custname = data(1)}
For Each cust In custs
    strCUSTNMBR = Replace(cust.custnmbr, Chr(34), "")
    strCUSTNAME = Replace(cust.custname, Chr(34), "")
Next
Nepa
fonte
3

A melhor opção que encontrei e resolve problemas em que você pode ter diferentes versões do Office instaladas, além de problemas de 32/64 bits como Chuck Bevitt mencionado , é o FileHelpers .

Ele pode ser adicionado às referências do seu projeto usando o NuGet e fornece uma solução de uma linha:

CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);
Neo
fonte
você pode dizer o que é o CommonEngine? O NuGet é o mesmo do NuGet.Core. I encontrado apenas NuGet.Core em referências
sindhu jampani
É FileHelpers que você precisa. Se você possui o NuGet, adicione-o ao NuGet. Caso contrário, basta adicioná-lo como uma montagem em seu projeto. O CommonEngine faz parte do FileHelpers.
Neo
3

Para aqueles que desejam não usar uma biblioteca externa e preferem não usar o OleDB, veja o exemplo abaixo. Tudo o que encontrei foi o OleDB, a biblioteca externa ou simplesmente a divisão com base em vírgula! Para o meu caso, o OleDB não estava funcionando, então eu queria algo diferente.

Encontrei um artigo de MarkJ que referenciava o método Microsoft.VisualBasic.FileIO.TextFieldParser como visto aqui . O artigo está escrito em VB e não retorna uma tabela de dados, portanto, veja meu exemplo abaixo.

public static DataTable LoadCSV(string path, bool hasHeader)
    {
        DataTable dt = new DataTable();

        using (var MyReader = new Microsoft.VisualBasic.FileIO.TextFieldParser(path))
        {
            MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
            MyReader.Delimiters = new String[] { "," };

            string[] currentRow;

            //'Loop through all of the fields in the file.  
            //'If any lines are corrupt, report an error and continue parsing.  
            bool firstRow = true;
            while (!MyReader.EndOfData)
            {
                try
                {
                    currentRow = MyReader.ReadFields();

                    //Add the header columns
                    if (hasHeader && firstRow)
                    {
                        foreach (string c in currentRow)
                        {
                            dt.Columns.Add(c, typeof(string));
                        }

                        firstRow = false;
                        continue;
                    }

                    //Create a new row
                    DataRow dr = dt.NewRow();
                    dt.Rows.Add(dr);

                    //Loop thru the current line and fill the data out
                    for(int c = 0; c < currentRow.Count(); c++)
                    {
                        dr[c] = currentRow[c];
                    }
                }
                catch (Microsoft.VisualBasic.FileIO.MalformedLineException ex)
                {
                    //Handle the exception here
                }
            }
        }

        return dt;
    }
Smeiff
fonte
3

Resposta muito básica: se você não tiver um csv complexo que possa usar uma função de divisão simples, isso funcionará bem para importação (observe que isso importa como seqüências de caracteres, eu faço conversões de tipo de dados mais tarde, se necessário)

 private DataTable csvToDataTable(string fileName, char splitCharacter)
    {                
        StreamReader sr = new StreamReader(fileName);
        string myStringRow = sr.ReadLine();
        var rows = myStringRow.Split(splitCharacter);
        DataTable CsvData = new DataTable();
        foreach (string column in rows)
        {
            //creates the columns of new datatable based on first row of csv
            CsvData.Columns.Add(column);
        }
        myStringRow = sr.ReadLine();
        while (myStringRow != null)
        {
            //runs until string reader returns null and adds rows to dt 
            rows = myStringRow.Split(splitCharacter);
            CsvData.Rows.Add(rows);
            myStringRow = sr.ReadLine();
        }
        sr.Close();
        sr.Dispose();
        return CsvData;
    }

Meu método, se estou importando uma tabela com um separador de seqüência de caracteres [] e lida com o problema em que a linha atual que estou lendo pode ter ido para a próxima linha no arquivo csv ou de texto <- IN, caso em que quero fazer um loop até obter para o número total de linhas na primeira linha (colunas)

public static DataTable ImportCSV(string fullPath, string[] sepString)
    {
        DataTable dt = new DataTable();
        using (StreamReader sr = new StreamReader(fullPath))
        {
           //stream uses using statement because it implements iDisposable
            string firstLine = sr.ReadLine();
            var headers = firstLine.Split(sepString, StringSplitOptions.None);
            foreach (var header in headers)
            {
               //create column headers
                dt.Columns.Add(header);
            }
            int columnInterval = headers.Count();
            string newLine = sr.ReadLine();
            while (newLine != null)
            {
                //loop adds each row to the datatable
                var fields = newLine.Split(sepString, StringSplitOptions.None); // csv delimiter    
                var currentLength = fields.Count();
                if (currentLength < columnInterval)
                {
                    while (currentLength < columnInterval)
                    {
                       //if the count of items in the row is less than the column row go to next line until count matches column number total
                        newLine += sr.ReadLine();
                        currentLength = newLine.Split(sepString, StringSplitOptions.None).Count();
                    }
                    fields = newLine.Split(sepString, StringSplitOptions.None);
                }
                if (currentLength > columnInterval)
                {  
                    //ideally never executes - but if csv row has too many separators, line is skipped
                    newLine = sr.ReadLine();
                    continue;
                }
                dt.Rows.Add(fields);
                newLine = sr.ReadLine();
            }
            sr.Close();
        }

        return dt;
    }
Matt Farguson
fonte
É bom que você não tenha declarado linhas como string [] ainda.
Animal Style
@AnimalStyle você está certo - atualizado com método mais robusto e linhas declarou
Matt Farguson
3

Modificado de Mr ChuckBevitt

Solução de trabalho:

string CSVFilePathName = APP_PATH + "Facilities.csv";
string[] Lines = File.ReadAllLines(CSVFilePathName);
string[] Fields;
Fields = Lines[0].Split(new char[] { ',' });
int Cols = Fields.GetLength(0);
DataTable dt = new DataTable();
//1st row must be column names; force lower case to ensure matching later on.
for (int i = 0; i < Cols-1; i++)
        dt.Columns.Add(Fields[i].ToLower(), typeof(string));
DataRow Row;
for (int i = 0; i < Lines.GetLength(0)-1; i++)
{
        Fields = Lines[i].Split(new char[] { ',' });
        Row = dt.NewRow();
        for (int f = 0; f < Cols-1; f++)
                Row[f] = Fields[f];
        dt.Rows.Add(Row);
}
Balaji Selvarajan
fonte
Então isso resolve um problema de memória, certo? Isso é processamento linha por linha e não persiste na memória, portanto não deve haver exceções? Eu gosto da maneira como isso é processado, mas o File.ReadAllLines () salva tudo na memória? Eu acho que você deveria usar File.ReadLines () para evitar um enorme buffer de memória? Esta é uma boa resposta para a pergunta em questão que eu só quero saber sobre problemas de memória.
DtechNet
2

Aqui está uma solução que usa o driver de texto ODBC do ADO.Net:

Dim csvFileFolder As String = "C:\YourFileFolder"
Dim csvFileName As String = "YourFile.csv"

'Note that the folder is specified in the connection string,
'not the file. That's specified in the SELECT query, later.
Dim connString As String = "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" _
    & csvFileFolder & ";Extended Properties=""Text;HDR=No;FMT=Delimited"""
Dim conn As New Odbc.OdbcConnection(connString)

'Open a data adapter, specifying the file name to load
Dim da As New Odbc.OdbcDataAdapter("SELECT * FROM [" & csvFileName & "]", conn)
'Then fill a data table, which can be bound to a grid
Dim dt As New DataTableda.Fill(dt)

grdCSVData.DataSource = dt

Depois de preenchido, você pode avaliar as propriedades da tabela de dados, como ColumnName, para utilizar todos os poderes dos objetos de dados ADO.Net.

No VS2008, você pode usar o Linq para obter o mesmo efeito.

NOTA: Isso pode ser uma duplicata desta questão de SO.

Bob Mc
fonte
2

Não resisto a adicionar minha própria rotação a isso. Isso é muito melhor e mais compacto do que o que eu usei no passado.

Esta solução:

  • Não depende de um driver de banco de dados ou de uma biblioteca de terceiros.
  • Não falhará em nomes de colunas duplicados
  • Manipula vírgulas nos dados
  • Manipula qualquer delimitador, não apenas vírgulas (embora esse seja o padrão)

Aqui está o que eu vim com:

  Public Function ToDataTable(FileName As String, Optional Delimiter As String = ",") As DataTable
    ToDataTable = New DataTable
    Using TextFieldParser As New Microsoft.VisualBasic.FileIO.TextFieldParser(FileName) With
      {.HasFieldsEnclosedInQuotes = True, .TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited, .TrimWhiteSpace = True}
      With TextFieldParser
        .SetDelimiters({Delimiter})
        .ReadFields.ToList.Unique.ForEach(Sub(x) ToDataTable.Columns.Add(x))
        ToDataTable.Columns.Cast(Of DataColumn).ToList.ForEach(Sub(x) x.AllowDBNull = True)
        Do Until .EndOfData
          ToDataTable.Rows.Add(.ReadFields.Select(Function(x) Text.BlankToNothing(x)).ToArray)
        Loop
      End With
    End Using
  End Function

Depende de um método de extensão ( Unique) para lidar com nomes de colunas duplicados, como minha resposta em Como anexar números exclusivos a uma lista de cadeias

E aqui está a BlankToNothingfunção auxiliar:

  Public Function BlankToNothing(ByVal Value As String) As Object 
    If String.IsNullOrEmpty(Value) Then Return Nothing
    Return Value
  End Function
toddmo
fonte
2

Com o Cinchoo ETL - uma biblioteca de código aberto, você pode converter facilmente arquivos CSV em DataTable com poucas linhas de código.

using (var p = new ChoCSVReader(** YOUR CSV FILE **)
     .WithFirstLineHeader()
    )
{
    var dt = p.AsDataTable();
}

Para mais informações, visite codeproject artigo .

Espero que ajude.

RajN
fonte
2
    private static DataTable LoadCsvData(string refPath)
    {
        var cfg = new Configuration() { Delimiter = ",", HasHeaderRecord = true };
        var result = new DataTable();
        using (var sr = new StreamReader(refPath, Encoding.UTF8, false, 16384 * 2))
        {
            using (var rdr = new CsvReader(sr, cfg))
            using (var dataRdr = new CsvDataReader(rdr))
            {
                result.Load(dataRdr);
            }
        }
        return result;
    }

usando: https://joshclose.github.io/CsvHelper/

Youssef Bouha
fonte
Observe que na versão 13 Configuration foi renomeada CsvConfiguration para evitar conflitos de espaço para nome. Demonstração desta resposta de trabalho: dotnetfiddle.net/sdwc6i
DBC
2

Eu uso uma biblioteca chamada ExcelDataReader, você pode encontrá-la no NuGet. Certifique-se de instalar o ExcelDataReader e a extensão ExcelDataReader.DataSet (esta última fornece o método AsDataSet necessário mencionado abaixo).

Eu encapsulei tudo em uma função, você pode copiá-lo diretamente no seu código. Dê um caminho para o arquivo CSV, ele fornece um conjunto de dados com uma tabela.

public static DataSet GetDataSet(string filepath)
{
   var stream = File.OpenRead(filepath);

   try
   {
       var reader = ExcelReaderFactory.CreateCsvReader(stream, new ExcelReaderConfiguration()
       {
           LeaveOpen = false
       });

       var result = reader.AsDataSet(new ExcelDataSetConfiguration()
       {
           // Gets or sets a value indicating whether to set the DataColumn.DataType 
           // property in a second pass.
           UseColumnDataType = true,

           // Gets or sets a callback to determine whether to include the current sheet
           // in the DataSet. Called once per sheet before ConfigureDataTable.
           FilterSheet = (tableReader, sheetIndex) => true,

           // Gets or sets a callback to obtain configuration options for a DataTable. 
           ConfigureDataTable = (tableReader) => new ExcelDataTableConfiguration()
           {
               // Gets or sets a value indicating the prefix of generated column names.
               EmptyColumnNamePrefix = "Column",

               // Gets or sets a value indicating whether to use a row from the 
               // data as column names.
               UseHeaderRow = true,

               // Gets or sets a callback to determine which row is the header row. 
               // Only called when UseHeaderRow = true.
               ReadHeaderRow = (rowReader) =>
               {
                   // F.ex skip the first row and use the 2nd row as column headers:
                   //rowReader.Read();
               },

               // Gets or sets a callback to determine whether to include the 
               // current row in the DataTable.
               FilterRow = (rowReader) =>
               {
                   return true;
               },

               // Gets or sets a callback to determine whether to include the specific
               // column in the DataTable. Called once per column after reading the 
               // headers.
               FilterColumn = (rowReader, columnIndex) =>
               {
                   return true;
               }
           }
       });

       return result;
   }
   catch (Exception ex)
   {
       return null;
   }
   finally
   {
       stream.Close();
       stream.Dispose();
   }
}
Dotnetsqlcoder
fonte
É 2020 e esta é uma ótima solução em comparação com algumas das respostas mais antigas aqui. É bem embalado e usa uma biblioteca popular e leve da NuGet. E é flexível - se o seu CSV estiver na memória, simplesmente passe-o como um MemoryStreamcaminho em vez de como um arquivo. O DataTable solicitado pelo OP é facilmente extraído do DataSet assim:result.Tables[0]
Tawab Wakil
1

Apenas compartilhando esses métodos de extensão, espero que possa ajudar alguém.

public static List<string> ToCSV(this DataSet ds, char separator = '|')
{
    List<string> lResult = new List<string>();

    foreach (DataTable dt in ds.Tables)
    {
        StringBuilder sb = new StringBuilder();
        IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(separator.ToString(), columnNames));

        foreach (DataRow row in dt.Rows)
        {
            IEnumerable<string> fields = row.ItemArray.Select(field =>
              string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
            sb.AppendLine(string.Join(separator.ToString(), fields));
        }

        lResult.Add(sb.ToString());
    }
    return lResult;
}

public static DataSet CSVtoDataSet(this List<string> collectionCSV, char separator = '|')
{
    var ds = new DataSet();

    foreach (var csv in collectionCSV)
    {
        var dt = new DataTable();

        var readHeader = false;
        foreach (var line in csv.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
        {
            if (!readHeader)
            {
                foreach (var c in line.Split(separator))
                    dt.Columns.Add(c);
            }
            else
            {
                dt.Rows.Add(line.Split(separator));
            }
        }

        ds.Tables.Add(dt);
    }

    return ds;
}
Akaize
fonte
0

Use isso, uma função resolve todos os problemas de vírgula e cita:

public static DataTable CsvToDataTable(string strFilePath)
    {

        if (File.Exists(strFilePath))
        {

            string[] Lines;
            string CSVFilePathName = strFilePath;

            Lines = File.ReadAllLines(CSVFilePathName);
            while (Lines[0].EndsWith(","))
            {
                Lines[0] = Lines[0].Remove(Lines[0].Length - 1);
            }
            string[] Fields;
            Fields = Lines[0].Split(new char[] { ',' });
            int Cols = Fields.GetLength(0);
            DataTable dt = new DataTable();
            //1st row must be column names; force lower case to ensure matching later on.
            for (int i = 0; i < Cols; i++)
                dt.Columns.Add(Fields[i], typeof(string));
            DataRow Row;
            int rowcount = 0;
            try
            {
                string[] ToBeContinued = new string[]{};
                bool lineToBeContinued = false;
                for (int i = 1; i < Lines.GetLength(0); i++)
                {
                    if (!Lines[i].Equals(""))
                    {
                        Fields = Lines[i].Split(new char[] { ',' });
                        string temp0 = string.Join("", Fields).Replace("\"\"", "");
                        int quaotCount0 = temp0.Count(c => c == '"');
                        if (Fields.GetLength(0) < Cols || lineToBeContinued || quaotCount0 % 2 != 0)
                        {
                            if (ToBeContinued.GetLength(0) > 0)
                            {
                                ToBeContinued[ToBeContinued.Length - 1] += "\n" + Fields[0];
                                Fields = Fields.Skip(1).ToArray();
                            }
                            string[] newArray = new string[ToBeContinued.Length + Fields.Length];
                            Array.Copy(ToBeContinued, newArray, ToBeContinued.Length);
                            Array.Copy(Fields, 0, newArray, ToBeContinued.Length, Fields.Length);
                            ToBeContinued = newArray;
                            string temp = string.Join("", ToBeContinued).Replace("\"\"", "");
                            int quaotCount = temp.Count(c => c == '"');
                            if (ToBeContinued.GetLength(0) >= Cols && quaotCount % 2 == 0 )
                            {
                                Fields = ToBeContinued;
                                ToBeContinued = new string[] { };
                                lineToBeContinued = false;
                            }
                            else
                            {
                                lineToBeContinued = true;
                                continue;
                            }
                        }

                        //modified by Teemo @2016 09 13
                        //handle ',' and '"'
                        //Deserialize CSV following Excel's rule:
                        // 1: If there is commas in a field, quote the field.
                        // 2: Two consecutive quotes indicate a user's quote.

                        List<int> singleLeftquota = new List<int>();
                        List<int> singleRightquota = new List<int>();

                        //combine fileds if number of commas match
                        if (Fields.GetLength(0) > Cols) 
                        {
                            bool lastSingleQuoteIsLeft = true;
                            for (int j = 0; j < Fields.GetLength(0); j++)
                            {
                                bool leftOddquota = false;
                                bool rightOddquota = false;
                                if (Fields[j].StartsWith("\"")) 
                                {
                                    int numberOfConsecutiveQuotes = 0;
                                    foreach (char c in Fields[j]) //start with how many "
                                    {
                                        if (c == '"')
                                        {
                                            numberOfConsecutiveQuotes++;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                    if (numberOfConsecutiveQuotes % 2 == 1)//start with odd number of quotes indicate system quote
                                    {
                                        leftOddquota = true;
                                    }
                                }

                                if (Fields[j].EndsWith("\""))
                                {
                                    int numberOfConsecutiveQuotes = 0;
                                    for (int jj = Fields[j].Length - 1; jj >= 0; jj--)
                                    {
                                        if (Fields[j].Substring(jj,1) == "\"") // end with how many "
                                        {
                                            numberOfConsecutiveQuotes++;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }

                                    if (numberOfConsecutiveQuotes % 2 == 1)//end with odd number of quotes indicate system quote
                                    {
                                        rightOddquota = true;
                                    }
                                }
                                if (leftOddquota && !rightOddquota)
                                {
                                    singleLeftquota.Add(j);
                                    lastSingleQuoteIsLeft = true;
                                }
                                else if (!leftOddquota && rightOddquota)
                                {
                                    singleRightquota.Add(j);
                                    lastSingleQuoteIsLeft = false;
                                }
                                else if (Fields[j] == "\"") //only one quota in a field
                                {
                                    if (lastSingleQuoteIsLeft)
                                    {
                                        singleRightquota.Add(j);
                                    }
                                    else
                                    {
                                        singleLeftquota.Add(j);
                                    }
                                }
                            }
                            if (singleLeftquota.Count == singleRightquota.Count)
                            {
                                int insideCommas = 0;
                                for (int indexN = 0; indexN < singleLeftquota.Count; indexN++)
                                {
                                    insideCommas += singleRightquota[indexN] - singleLeftquota[indexN];
                                }
                                if (Fields.GetLength(0) - Cols >= insideCommas) //probabaly matched
                                {
                                    int validFildsCount = insideCommas + Cols; //(Fields.GetLength(0) - insideCommas) may be exceed the Cols
                                    String[] temp = new String[validFildsCount];
                                    int totalOffSet = 0;
                                    for (int iii = 0; iii < validFildsCount - totalOffSet; iii++)
                                    {
                                        bool combine = false;
                                        int storedIndex = 0;
                                        for (int iInLeft = 0; iInLeft < singleLeftquota.Count; iInLeft++)
                                        {
                                            if (iii + totalOffSet == singleLeftquota[iInLeft])
                                            {
                                                combine = true;
                                                storedIndex = iInLeft;
                                                break;
                                            }
                                        }
                                        if (combine)
                                        {
                                            int offset = singleRightquota[storedIndex] - singleLeftquota[storedIndex];
                                            for (int combineI = 0; combineI <= offset; combineI++)
                                            {
                                                temp[iii] += Fields[iii + totalOffSet + combineI] + ",";
                                            }
                                            temp[iii] = temp[iii].Remove(temp[iii].Length - 1, 1);
                                            totalOffSet += offset;
                                        }
                                        else
                                        {
                                            temp[iii] = Fields[iii + totalOffSet];
                                        }
                                    }
                                    Fields = temp;
                                }
                            }
                        }
                        Row = dt.NewRow();
                        for (int f = 0; f < Cols; f++)
                        {
                            Fields[f] = Fields[f].Replace("\"\"", "\""); //Two consecutive quotes indicate a user's quote
                            if (Fields[f].StartsWith("\""))
                            {
                                if (Fields[f].EndsWith("\""))
                                {
                                    Fields[f] = Fields[f].Remove(0, 1);
                                    if (Fields[f].Length > 0)
                                    {
                                        Fields[f] = Fields[f].Remove(Fields[f].Length - 1, 1);
                                    }
                                }
                            }
                            Row[f] = Fields[f];
                        }
                        dt.Rows.Add(Row);
                        rowcount++;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception( "row: " + (rowcount+2) + ", " + ex.Message);
            }
            //OleDbConnection connection = new OleDbConnection(string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}; Extended Properties=""text;HDR=Yes;FMT=Delimited"";", FilePath + FileName));
            //OleDbCommand command = new OleDbCommand("SELECT * FROM " + FileName, connection);
            //OleDbDataAdapter adapter = new OleDbDataAdapter(command);
            //DataTable dt = new DataTable();
            //adapter.Fill(dt);
            //adapter.Dispose();
            return dt;
        }
        else
            return null;

        //OleDbConnection connection = new OleDbConnection(string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}; Extended Properties=""text;HDR=Yes;FMT=Delimited"";", strFilePath));
        //OleDbCommand command = new OleDbCommand("SELECT * FROM " + strFileName, connection);
        //OleDbDataAdapter adapter = new OleDbDataAdapter(command);
        //DataTable dt = new DataTable();
        //adapter.Fill(dt);
        //return dt;
    }
Teemo
fonte
0
 Public Function ReadCsvFileToDataTable(strFilePath As String) As DataTable
    Dim dtCsv As DataTable = New DataTable()
    Dim Fulltext As String
    Using sr As StreamReader = New StreamReader(strFilePath)
        While Not sr.EndOfStream
            Fulltext = sr.ReadToEnd().ToString()
            Dim rows As String() = Fulltext.Split(vbLf)
            For i As Integer = 0 To rows.Count() - 1 - 1
                Dim rowValues As String() = rows(i).Split(","c)
                If True Then
                    If i = 0 Then
                        For j As Integer = 0 To rowValues.Count() - 1
                            dtCsv.Columns.Add(rowValues(j))
                        Next
                    Else
                        Dim dr As DataRow = dtCsv.NewRow()
                        For k As Integer = 0 To rowValues.Count() - 1
                            dr(k) = rowValues(k).ToString()
                        Next
                        dtCsv.Rows.Add(dr)
                    End If
                End If
            Next
        End While
    End Using
    Return dtCsv
End Function
Esat Kiziltepe
fonte
0

Recentemente, escrevi um analisador de CSV para .NET que estou afirmando ser atualmente o mais rápido disponível como pacote de nuget : Sylvan.Data.Csv .

Usar esta biblioteca para carregar um DataTableé extremamente fácil.

using var tr = File.OpenText("data.csv");
using var dr = CsvDataReader.Create(tr);
var dt = new DataTable();
dt.Load(dr);

Supondo que seu arquivo seja um arquivo separado por vírgula padrão com cabeçalhos, é tudo o que você precisa. Também existem opções para permitir a leitura de arquivos sem cabeçalhos e o uso de delimitadores alternativos etc.

Também é possível fornecer um esquema personalizado para o arquivo CSV, para que as colunas possam ser tratadas como algo diferente de stringvalores. Isso permitirá que as DataTablecolunas sejam carregadas com valores que possam ser mais fáceis de trabalhar, pois você não precisará coagi-los quando acessá-los.

var schema = new TypedCsvSchema();
schema.Add(0, typeof(int));
schema.Add(1, typeof(string));
schema.Add(2, typeof(double?));
schema.Add(3, typeof(DateTime));
schema.Add(4, typeof(DateTime?));

var options = new CsvDataReaderOptions { 
    Schema = schema 
};

using var tr = GetData();
using var dr = CsvDataReader.Create(tr, options);

TypedCsvSchema é uma implementação de ICsvSchemaProvider que fornece uma maneira simples de definir os tipos de colunas. No entanto, também é possível fornecer um costume ICsvSchemaProviderquando você deseja fornecer mais metadados, como exclusividade ou tamanho da coluna restrita, etc.

MarkPflug
fonte