Cómo leer un archivo CSV en una tabla de datos .NET

170

¿Cómo puedo cargar un archivo CSV en un System.Data.DataTable, creando la tabla de datos basada en el archivo CSV?

¿La funcionalidad regular de ADO.net permite esto?

Ronnie Overby
fuente
21
¿Cómo es esto posiblemente fuera de tema? Es una pregunta específica y 100 personas lo encuentran útil
Ryan
10
@ Ryan: En verdad te digo ... Los moderadores de StackOverflow son una cría de víboras. ¡Apártate de mí, moderadores de StackOverflow!
Ronnie Overby

Respuestas:

89

Aquí hay una clase excelente que copiará los datos CSV en una tabla de datos utilizando la estructura de los datos para crear la tabla de datos:

Un analizador genérico portátil y eficiente para archivos planos

Es fácil de configurar y fácil de usar. Te insto a que eches un vistazo.

Jay Riggs
fuente
Excelente de hecho. Funcionó perfectamente para mí fuera de la caja, sin siquiera leer la documentación.
smirkingman
¿Funcionará esto en archivos CSV donde cada fila puede ser de una estructura diferente? Tengo un archivo de registro con diferentes tipos de eventos registrados que deberían separarse en varias tablas.
gonzobrains
2
@gonzobrains - Probablemente no; La suposición básica de un archivo CSV es una estructura de datos rectangular basada en un único conjunto de encabezados de columna especificados en la primera línea. Lo que tiene parece ser datos discriminados, delimitados por comas, más genéricos, que requieren "ETL" más sofisticados para analizar desde el archivo a instancias de objetos de diversos tipos (que podrían incluir filas de datos de diferentes tablas de datos).
KeithS
93

He estado usando el OleDbproveedor. Sin embargo, tiene problemas si está leyendo en filas que tienen valores numéricos pero desea que se traten como texto. Sin embargo, puede solucionar ese problema creando un schema.iniarchivo. Aquí está mi método que utilicé:

// 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
fuente
Gracias amigo. Eso me ayudó. Tenía un archivo CSV en el que las comas no solo eran separadores, estaban en todas partes dentro de muchos valores de columnas, por lo que crear una expresión regular que dividiría la línea fue un poco desafiante. OleDbProvider dedujo el esquema correctamente.
Galilyou
La implementación tiene sentido, pero ¿cómo manejamos las celdas que contienen tipos de datos mixtos? Por ejemplo, 40C y etc.
GKED
GKED, si los datos que está leyendo siempre tienen un conjunto esperado de columnas y tipos, puede colocar en la misma carpeta un archivo shema.ini que le brinde información sobre las columnas a los proveedores de OleDb. Aquí hay un enlace a un artículo de Microsoft que proporciona detalles sobre cómo estructurar el archivo. msdn.microsoft.com/en-us/library/…
Jim Scott
44
Si bien esta respuesta funcionará, recomendaría encarecidamente que no lo haga. Introduce una dependencia externa que puede entrar en conflicto con otras instalaciones de oficina en la misma máquina (¿usar Excel en su entorno de desarrollo local?), Dependiendo de las versiones instaladas. Existen paquetes NuGet (ExcelDataReader, CsvHelper) que hacen esto de manera más eficiente y portátil.
A. Murray el
1
@ A.Murray - ¿Qué quieres decir exactamente? Esto utiliza el proveedor OleDb integrado en System.Data.dll. No necesita instalar ningún "controlador" adicional. Y me sorprendería hoy en día si alguna instalación de Windows no tuviera instalado el controlador Jet básico. Este es el CSV de 1990 ...
Paul Easter
40

He decidido usar el lector de Csv de Sebastien Lorion .

La sugerencia de Jay Riggs también es una gran solución, pero no necesitaba todas las características que proporciona el analizador genérico de Andrew Rissing .

ACTUALIZACIÓN 25/10/2010

Después de usar el Lector de Csv de Sebastien Lorion en mi proyecto durante casi un año y medio, descubrí que arroja excepciones al analizar algunos archivos csv que creo que están bien formados.

Entonces, cambié al analizador genérico de Andrew Rissing y parece estar mucho mejor.

ACTUALIZACIÓN 22/09/2014

En estos días, utilizo principalmente este método de extensión para leer texto delimitado:

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

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

ACTUALIZACIÓN 20/02/2015

Ejemplo:

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
fuente
Estoy de acuerdo en que el lector CSV de Sebastien Lorien es genial. Lo uso para el procesamiento pesado de CSV, pero también he usado Andrew's Rissing para pequeños trabajos y me ha servido bien. ¡Que te diviertas!
Jay Riggs
¿Cómo puedo usar estas clases para cargar CSV en DATATABLE?
Muflix
Intenté esto, pero la colección it.Current.Keys regresa con "System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]" en lugar del nombre de la columna. ¿Alguna idea de por qué?
user3658298
¿Puedes usar delimitadores de caracteres múltiples?
lanza el
No, pero pensé en habilitar eso.
Ronnie Overby el
32

Hola, está funcionando al 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;
   }

Imagen CSV ingrese la descripción de la imagen aquí

Tabla de datos importada ingrese la descripción de la imagen aquí

Shivam Srivastava
fuente
77
Solo cuando el 100% de las entradas son los archivos CSV más simples (lo que puede ser cierto en su caso).
Ronnie Overby el
Estás en lo correcto. deberías usar codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll) Intenté que funcionara bien.
Shivam Srivastava
1
Ver mi respuesta de 2009.
Ronnie Overby
1
@ShivamSrivastava Recibo el error en la última fila, ¿estás ahí y luego te doy otra información de contacto
Sunil Acharya
Aunque no utilicé esta versión exactamente, se basó en que resolví mi problema. Gracias. Funciona muy bien
nrod el
13

Siempre solíamos usar el controlador Jet.OLEDB, hasta que comenzamos a utilizar aplicaciones de 64 bits. Microsoft no ha lanzado ni lanzará un controlador Jet de 64 bits. Aquí hay una solución simple que se nos ocurrió que usa File.ReadAllLines y String.Split para leer y analizar el archivo CSV y cargar manualmente una DataTable. Como se señaló anteriormente, NO maneja la situación en la que uno de los valores de la columna contiene una coma. Usamos esto principalmente para leer archivos de configuración personalizados; lo bueno de usar archivos CSV es que podemos editarlos en 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
fuente
8

este es el código que uso, pero sus aplicaciones deben ejecutarse con la versión neta 3.5

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
fuente
Esto es más o menos lo que quería presentar.
Capitán Kenpachi
8

Puede lograrlo usando Microsoft.VisualBasic.FileIO.TextFieldParser dll en 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
fuente
No intente reinventar la rueda con procesamiento CSV. Hay tantas alternativas excelentes de código abierto que son muy robustas.
Mike Cole
1
Gracias Brad, consejo útil en relación con TextFieldParser por manejar las comillas incrustadas.
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
fuente
3

Me encontré con este código que usa Linq y regex para analizar un archivo CSV. El artículo de referencia ahora tiene más de un año y medio, pero no ha encontrado una forma más ordenada de analizar un CSV usando Linq (y regex) que esto. La advertencia es que la expresión regular aplicada aquí es para archivos delimitados por comas (¡detectará comas dentro de comillas!) Y que puede que no se adapte bien a los encabezados, pero hay una manera de superarlos). Toma un 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
Nepal
fuente
3

FileHelpers es la mejor opción que he encontrado, y resuelve problemas en los que puede tener instaladas diferentes versiones de Office, y también problemas de 32/64 bits como Chuck Bevitt .

Se puede agregar a las referencias de su proyecto usando NuGet y proporciona una solución de una sola línea:

CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);
Neo
fuente
¿Puedes decir qué es CommonEngine? NuGet es lo mismo que NuGet.Core. Encontré solo NuGet. Core en las referencias
sindhu jampani
Es FileHelpers lo que necesitas. Si tiene NuGet, agréguelo con NuGet. De lo contrario, simplemente agréguelo como un ensamblaje en su proyecto. CommonEngine es parte de FileHelpers.
Neo
3

Para aquellos de ustedes que no desean usar una biblioteca externa y prefieren no usar OleDB, consulte el siguiente ejemplo. ¡Todo lo que encontré fue OleDB, una biblioteca externa, o simplemente dividir en base a una coma! Para mi caso, OleDB no funcionaba, así que quería algo diferente.

Encontré un artículo de MarkJ que hacía referencia al método Microsoft.VisualBasic.FileIO.TextFieldParser como se ve aquí . El artículo está escrito en VB y no devuelve una tabla de datos, así que vea mi ejemplo a continuación.

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
fuente
3

Respuesta muy básica: si no tiene un csv complejo que pueda usar una función de división simple, esto funcionará bien para importar (tenga en cuenta que esto importa como cadenas, luego haré conversiones de tipo de datos si es necesario)

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

Mi método si estoy importando una tabla con un separador de cadena [] y maneja el problema donde la línea actual que estoy leyendo puede haber ido a la siguiente línea en el archivo csv o de texto <- EN cuyo caso quiero hacer un bucle hasta que llegue al número total de líneas en la primera fila (columnas)

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
fuente
Es bueno que todavía no hayas declarado filas como cadena [].
Animal Style
@AnimalStyle tienes razón - actualizado con un método más robusto y filas declaradas
Matt Farguson
3

Modificado de Mr ChuckBevitt

Solución de trabajo:

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
fuente
Entonces esto resuelve un problema de memoria ¿verdad? Este es el procesamiento línea por línea y no persiste en la memoria, por lo que no debería haber excepciones. Me gusta la forma en que se procesa, pero ¿File.ReadAllLines () no guarda todo en la memoria? Creo que se supone que debes usar File.ReadLines () para evitar un gran búfer de memoria? Esta es una buena respuesta para la pregunta en cuestión que solo quiero saber sobre las preocupaciones relacionadas con la memoria.
DtechNet
2

Aquí hay una solución que utiliza el controlador de texto ODBC de 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

Una vez rellenado, puede valorar las propiedades de la tabla de datos, como ColumnName, para utilizar todos los poderes de los objetos de datos ADO.Net.

En VS2008 puede usar Linq para lograr el mismo efecto.

NOTA: Esto puede ser un duplicado de esta pregunta SO.

Bob Mc
fuente
2

No puedo resistirme a agregar mi propio giro a esto. Esto es mucho mejor y más compacto que lo que he usado en el pasado.

Esta solución:

  • No depende de un controlador de base de datos o una biblioteca de terceros.
  • No fallará en nombres de columna duplicados
  • Maneja comas en los datos
  • Maneja cualquier delimitador, no solo comas (aunque ese es el valor predeterminado)

Esto es lo que se me ocurrió:

  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 un método de extensión ( Unique) para manejar nombres de columnas duplicados que se encontrarán como mi respuesta en Cómo agregar números únicos a una lista de cadenas

Y aquí está la BlankToNothingfunción auxiliar:

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

Con Cinchoo ETL , una biblioteca de código abierto, puede convertir fácilmente archivos CSV a DataTable con pocas líneas de código.

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

Para obtener más información, visite el artículo de codeproject .

Espero eso ayude.

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

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

Youssef Bouha
fuente
Tenga en cuenta que en la versión 13 Configuration se cambió el nombre a CsvConfiguration para evitar conflictos de espacio de nombres. Demostración de esta respuesta funcionando: dotnetfiddle.net/sdwc6i
dbc
2

Utilizo una biblioteca llamada ExcelDataReader, puedes encontrarla en NuGet. Asegúrese de instalar tanto ExcelDataReader como la extensión ExcelDataReader.DataSet (esta última proporciona el método AsDataSet requerido al que se hace referencia a continuación).

Encapsulé todo en una función, puedes copiarlo en tu código directamente. Dale una ruta al archivo CSV, te da un conjunto de datos con una tabla.

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
fuente
Es 2020 y esta es una gran solución en comparación con algunas de las respuestas anteriores aquí. Está bien empaquetado y utiliza una biblioteca popular y ligera de NuGet. Y es flexible: si su CSV está en la memoria, simplemente páselo como una MemoryStreamruta de archivo en lugar de hacerlo. El DataTable que OP solicitó se extrae fácilmente del DataSet de esta manera:result.Tables[0]
Tawab Wakil
1

Solo compartiendo estos métodos de extensión, espero que pueda ayudar a alguien.

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
fuente
0

Use esto, una función resuelve todos los problemas de coma y 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
fuente
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
fuente
0

Recientemente escribí un analizador CSV para .NET que , según afirmo, es actualmente el más rápido disponible como paquete nuget : Sylvan.Data.Csv .

Usar esta biblioteca para cargar a DataTablees extremadamente fácil.

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

Suponiendo que su archivo es un archivo estándar separado por comas con encabezados, eso es todo lo que necesita. También hay opciones para permitir la lectura de archivos sin encabezados y el uso de delimitadores alternativos, etc.

También es posible proporcionar un esquema personalizado para el archivo CSV para que las columnas puedan tratarse como algo distinto de los stringvalores. Esto permitirá que las DataTablecolumnas se carguen con valores que pueden ser más fáciles de trabajar, ya que no tendrá que forzarlos cuando acceda a ellos.

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

TypedCsvSchemaes una implementación de la ICsvSchemaProvidercual proporciona una manera simple de definir los tipos de las columnas. Sin embargo, también es posible proporcionar una personalización ICsvSchemaProvidercuando desee proporcionar más metadatos, como unicidad o tamaño de columna restringido, etc.

MarkPflug
fuente