Comment lire un fichier CSV dans une table de données .NET


170

Comment puis-je charger un fichier CSV dans un System.Data.DataTable, en créant la table de données basée sur le fichier CSV?

La fonctionnalité ADO.net habituelle le permet-elle?


21
Comment est-ce possible «hors sujet»? C'est une question spécifique et 100 personnes la trouvent utile
Ryan

10
@Ryan: En vérité, je vous le dis ... Les modérateurs de StackOverflow sont une couvée de vipères. Soyez derrière moi, modérateurs de StackOverflow!
Ronnie Overby

Réponses:


89

Voici une excellente classe qui copiera les données CSV dans une table de données en utilisant la structure des données pour créer le DataTable:

Un analyseur générique portable et efficace pour les fichiers plats

Il est facile à configurer et à utiliser. Je vous conseille vivement de jeter un œil.


Excellent en effet. Cela a parfaitement fonctionné pour moi hors de la boîte, sans même lire la documentation.
Smirkingman

Cela fonctionnera-t-il sur les fichiers CSV où chaque ligne peut avoir une structure différente? J'ai un fichier journal avec différents types d'événements enregistrés qui devraient être séparés en plusieurs tables.
gonzobrains

2
@gonzobrains - Probablement pas; L'hypothèse de base d'un fichier CSV est une structure de données rectangulaire basée sur un seul ensemble d'en-têtes de colonne spécifié dans la première ligne. Ce que vous avez semble être des données plus génériques délimitées par des virgules et discriminées, nécessitant un «ETL» plus sophistiqué pour analyser le fichier en instances d'objet de différents types (qui pourraient inclure des DataRows de différents DataTables).
KeithS

93

J'utilise le OleDbfournisseur. Cependant, cela pose des problèmes si vous lisez des lignes contenant des valeurs numériques mais que vous souhaitez qu'elles soient traitées comme du texte. Cependant, vous pouvez contourner ce problème en créant un schema.inifichier. Voici ma méthode que j'ai utilisée:

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

Merci mon pote. Cela m'a aidé. J'avais un fichier CSV dans lequel les virgules n'étaient pas seulement des séparateurs, elles étaient partout dans de nombreuses valeurs de colonnes, donc trouver une expression régulière qui diviserait la ligne était un peu difficile. OleDbProvider a correctement déduit le schéma.
Galilyou

L'implémentation a du sens, mais comment traiter les cellules contenant des types de données mixtes. Par exemple, 40C et etc.?
GKED

GKED, si les données que vous lisez ont toujours un ensemble attendu de colonnes et de types, vous pouvez placer dans le même dossier un fichier shema.ini qui indique les informations du fournisseur OleDb sur les colonnes. Voici un lien vers un article Microsoft qui fournit des détails sur la structure du fichier. msdn.microsoft.com/en-us/library/…
Jim Scott

4
Bien que cette réponse fonctionne, je vous déconseille fortement. Vous introduisez une dépendance externe qui peut entrer en conflit avec d'autres installations de bureau sur la même machine (utiliser Excel sur votre environnement de développement local?), En fonction des versions installées. Il existe des packages NuGet (ExcelDataReader, CsvHelper) qui le font de manière plus efficace et plus portable.
A. Murray

1
@ A.Murray - Que voulez-vous dire exactement? Cela utilise le fournisseur OleDb intégré dans System.Data.dll. Vous n'avez pas besoin d'installer de "pilotes" supplémentaires. Et je serais choqué à cette époque si une installation de Windows n'avait pas le pilote Jet de base installé. Ceci est le CSV des années 1990 ....
Paul Easter

40

J'ai décidé d'utiliser le lecteur Csv de Sébastien Lorion .

La suggestion de Jay Riggs est également une excellente solution, mais je n'avais tout simplement pas besoin de toutes les fonctionnalités fournies par Generic Parser d'Andrew Rissing .

MISE À JOUR 25/10/2010

Après avoir utilisé le lecteur Csv de Sébastien Lorion dans mon projet pendant près d'un an et demi, j'ai constaté qu'il lançait des exceptions lors de l'analyse de certains fichiers csv que je pense être bien formés.

Donc, je suis passé à l' analyseur générique d'Andrew Rissing et il semble aller beaucoup mieux.

MISE À JOUR 22/09/2014

Ces jours-ci, j'utilise principalement cette méthode d'extension pour lire du texte délimité:

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

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

MISE À JOUR 20/02/2015

Exemple:

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

Je suis d'accord que le lecteur CSV de Sébastien Lorien est génial. Je l'utilise pour le traitement CSV lourd, mais j'ai également utilisé Andrew's Rissing's pour les petits travaux et cela m'a bien servi. S'amuser!
Jay Riggs

Comment puis-je utiliser ces classes pour charger CSV dans DATATABLE?
Muflix

J'ai essayé cela mais la collection it.Current.Keys retourne avec "System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]" plutôt que le nom de la colonne. Des pensées sur pourquoi?
user3658298

Pouvez-vous utiliser des délimiteurs à plusieurs caractères?
lance le

Non, mais j'ai pensé à permettre cela.
Ronnie Over par

32

Hey ça marche à 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;
   }

Image CSV entrez la description de l'image ici

Table de données importée entrez la description de l'image ici


7
Uniquement lorsque 100% des entrées sont les plus simples des fichiers CSV (ce qui peut être vrai dans votre cas).
Ronnie Overby le

Vous avez raison. vous devez utiliser codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll) J'ai bien essayé son fonctionnement.
Shivam Srivastava

1
Voir ma réponse de 2009.
Ronnie Overby

1
@ShivamSrivastava J'obtiens l'erreur dans la dernière ligne êtes-vous là alors donnez-vous d'autres informations de contact
Sunil Acharya

Bien que je n'ai pas utilisé exactement cette version, c'est sur cette base que j'ai résolu mon problème. Je vous remercie. Fonctionne très bien.
nrod

13

Nous avons toujours utilisé le pilote Jet.OLEDB, jusqu'à ce que nous commencions à passer aux applications 64 bits. Microsoft n'a pas et ne publiera pas de pilote Jet 64 bits. Voici une solution simple que nous avons proposée qui utilise File.ReadAllLines et String.Split pour lire et analyser le fichier CSV et charger manuellement un DataTable. Comme indiqué ci-dessus, il NE gère PAS la situation où l'une des valeurs de colonne contient une virgule. Nous l'utilisons principalement pour lire des fichiers de configuration personnalisés - l'avantage de l'utilisation des fichiers CSV est que nous pouvons les modifier dans 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);
}

8

c'est le code que je l'utilise mais vos applications doivent fonctionner avec la version 3.5 nette

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

C'est à peu près ce que je voulais présenter.
Captain Kenpachi

8

Vous pouvez y parvenir en utilisant 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;
        }

N'essayez pas de réinventer la roue avec le traitement CSV. Il y a tellement de bonnes alternatives open source qui sont très robustes.
Mike Cole

1
Merci Brad, une astuce utile par rapport au TextFieldParser pour la gestion des citations intégrées.
matpm

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

3

Je suis tombé sur ce morceau de code qui utilise Linq et regex pour analyser un fichier CSV. L'article de référence a maintenant plus d'un an et demi, mais n'a pas trouvé de moyen plus propre d'analyser un CSV en utilisant Linq (et regex) que celui-ci. La mise en garde est que le regex appliqué ici est pour les fichiers délimités par des virgules (détectera les virgules entre guillemets!) Et qu'il peut ne pas convenir aux en-têtes, mais il existe un moyen de les surmonter). Prenez un pic:

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

3

La meilleure option que j'ai trouvée, et elle résout les problèmes où vous pouvez avoir différentes versions d'Office installées, ainsi que les problèmes 32/64 bits comme Chuck Bevitt mentionné , est FileHelpers .

Il peut être ajouté à vos références de projet à l'aide de NuGet et fournit une solution à une ligne:

CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);

pouvez-vous dire ce qu'est CommonEngine? NuGet est-il identique à NuGet.Core. Je n'ai trouvé que NuGet.Core dans les références
sindhu jampani

Ce sont FileHelpers dont vous avez besoin. Si vous avez NuGet, ajoutez-le avec NuGet. Sinon, ajoutez-le simplement en tant qu'assemblage dans votre projet. CommonEngine fait partie de FileHelpers.
Neo

3

Pour ceux d'entre vous qui souhaitent ne pas utiliser de bibliothèque externe et préfèrent ne pas utiliser OleDB, consultez l'exemple ci-dessous. Tout ce que j'ai trouvé était soit OleDB, une bibliothèque externe, soit simplement un fractionnement basé sur une virgule! Pour mon cas, OleDB ne fonctionnait pas donc je voulais quelque chose de différent.

J'ai trouvé un article de MarkJ qui faisait référence à la méthode Microsoft.VisualBasic.FileIO.TextFieldParser comme on le voit ici . L'article est écrit en VB et ne renvoie pas de datatable, alors voyez mon exemple ci-dessous.

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

3

Réponse très basique: si vous n'avez pas de csv complexe pouvant utiliser une fonction de fractionnement simple, cela fonctionnera bien pour l'importation (notez que cela importe sous forme de chaînes, je fais des conversions de type de données plus tard si nécessaire)

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

Ma méthode si j'importe une table avec un séparateur de chaîne [] et gère le problème où la ligne actuelle que je lis peut être passée à la ligne suivante dans le fichier csv ou texte <- auquel cas je veux faire une boucle jusqu'à ce que j'obtienne au nombre total de lignes de la première ligne (colonnes)

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

Bien que vous n'ayez pas encore déclaré les lignes comme chaîne [].
Animal Style

@AnimalStyle vous avez raison - mis à jour avec une méthode plus robuste et des lignes déclarées
Matt Farguson

3

Modifié de M. ChuckBevitt

Solution de travail:

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

Donc, cela résout un problème de mémoire, non? Il s'agit d'un traitement ligne par ligne et qui ne persiste pas en mémoire, il ne devrait donc y avoir aucune exception? J'aime la façon dont cela est traité mais File.ReadAllLines () ne sauvegarde-t-il pas tout en mémoire? Je pense que vous êtes censé utiliser File.ReadLines () pour éviter une énorme mémoire tampon? C'est une bonne réponse à la question que je veux juste savoir sur les problèmes de mémoire.
DtechNet

2

Voici une solution qui utilise le pilote de texte ODBC d'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

Une fois rempli, vous pouvez valoriser les propriétés de la table de données, comme ColumnName, pour utiliser toutes les puissances des objets de données ADO.Net.

Dans VS2008, vous pouvez utiliser Linq pour obtenir le même effet.

REMARQUE: cela peut être un double de cette question SO.


2

Je ne peux pas résister à ajouter mon propre spin à cela. C'est tellement mieux et plus compact que ce que j'ai utilisé dans le passé.

Cette solution:

  • Ne dépend pas d'un pilote de base de données ou d'une bibliothèque tierce.
  • N'échouera pas sur les noms de colonne en double
  • Gère les virgules dans les données
  • Gère n'importe quel délimiteur, pas seulement des virgules (bien que ce soit la valeur par défaut)

Voici ce que j'ai trouvé:

  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

Cela dépend d'une méthode d'extension ( Unique) pour gérer les noms de colonnes en double à trouver comme réponse dans Comment ajouter des numéros uniques à une liste de chaînes

Et voici la BlankToNothingfonction d'assistance:

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

2

Avec Cinchoo ETL - une bibliothèque open source, vous pouvez facilement convertir un fichier CSV en DataTable avec quelques lignes de code.

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

Pour plus d'informations, veuillez consulter l' article codeproject .

J'espère que ça aide.


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

en utilisant: https://joshclose.github.io/CsvHelper/


Notez que dans la version 13 a Configuration été renommé CsvConfiguration pour éviter les conflits d'espace de noms. Démo de cette réponse fonctionnant: dotnetfiddle.net/sdwc6i
dbc

2

J'utilise une bibliothèque appelée ExcelDataReader, vous pouvez la trouver sur NuGet. Assurez-vous d'installer à la fois ExcelDataReader et l'extension ExcelDataReader.DataSet (cette dernière fournit la méthode AsDataSet requise référencée ci-dessous).

J'ai tout encapsulé dans une seule fonction, vous pouvez le copier directement dans votre code. Donnez-lui un chemin vers le fichier CSV, il vous obtient un ensemble de données avec une table.

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

Nous sommes en 2020 et c'est une excellente solution par rapport à certaines des réponses les plus anciennes ici. Il est joliment emballé et utilise une bibliothèque populaire et légère de NuGet. Et c'est flexible - si votre CSV est en mémoire, transmettez-le simplement en tant que MemoryStreamchemin d'accès au fichier. Le DataTable demandé par OP est facilement extrait du DataSet comme ceci:result.Tables[0]
Tawab Wakil

1

En partageant simplement ces méthodes d'extension, j'espère que cela pourra aider quelqu'un.

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

0

Utilisez ceci, une fonction résout tous les problèmes de virgule et de citation:

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

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

0

J'ai récemment écrit un analyseur CSV pour .NET qui, selon moi, est actuellement le plus rapide disponible en tant que package nuget : Sylvan.Data.Csv .

Utiliser cette bibliothèque pour charger un DataTableest extrêmement simple.

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

En supposant que votre fichier soit un fichier standard séparé par des virgules avec des en-têtes, c'est tout ce dont vous avez besoin. Il existe également des options permettant de lire des fichiers sans en-têtes, d'utiliser des délimiteurs alternatifs, etc.

Il est également possible de fournir un schéma personnalisé pour le fichier CSV afin que les colonnes puissent être traitées comme autre chose que des stringvaleurs. Cela permettra aux DataTablecolonnes d'être chargées avec des valeurs qui peuvent être plus faciles à utiliser, car vous n'aurez pas à les contraindre lorsque vous y accédez.

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

TypedCsvSchemaest une implémentation ICsvSchemaProviderqui fournit un moyen simple de définir les types des colonnes. Cependant, il est également possible de fournir une personnalisation ICsvSchemaProviderlorsque vous souhaitez fournir plus de métadonnées, telles que l'unicité ou la taille de colonne contrainte, etc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.