web-dev-qa-db-fra.com

Analyse des fichiers CSV en C #, avec en-tête

Existe-t-il une méthode par défaut/officielle/recommandée pour analyser les fichiers CSV en C #? Je ne veux pas lancer mon propre analyseur.

De plus, j'ai vu des exemples de personnes utilisant ODBC/OLE DB pour lire des fichiers CSV via le pilote de texte, et de nombreuses personnes découragent cela en raison de ses "inconvénients". Quels sont ces inconvénients?

Idéalement, je cherche un moyen de lire le fichier CSV par nom de colonne, en utilisant le premier enregistrement comme nom d'en-tête/champ. Certaines des réponses fournies sont correctes, mais permettent de désérialiser le fichier en classes.

220
David Pfeffer

Laissez une bibliothèque gérer tous les détails essentiels pour vous! :-)

Consultez FileHelpers et restez DRY - Ne vous répétez pas - pas besoin de réinventer la roue un million de fois ....

En gros, il vous suffit de définir la forme de vos données - les champs de votre ligne individuelle dans le fichier CSV - au moyen d’une classe publique (et d’attributs bien pensés tels que les valeurs par défaut, le remplacement des valeurs NULL, etc.) le moteur FileHelpers dans un fichier et le bingo - vous récupérez toutes les entrées de ce fichier. Une opération simple - une grande performance!

121
marc_s

Un analyseur CSV fait maintenant partie de .NET Framework.

Ajouter une référence à Microsoft.VisualBasic.dll (fonctionne très bien en C #, peu importe le nom)

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

Les documents sont ici - Classe TextFieldParser

288
Alex

CsvHelper (une bibliothèque que je maintiens) lira un fichier CSV en objets personnalisés.

var csv = new CsvReader( File.OpenText( "file.csv" ) );
var myCustomObjects = csv.GetRecords<MyCustomObject>();

Parfois, vous ne possédez pas les objets que vous essayez de lire. Dans ce cas, vous pouvez utiliser le mappage fluide car vous ne pouvez pas attribuer d'attributs à la classe.

public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject>
{
    public MyCustomObjectMap()
    {
        Map( m => m.Property1 ).Name( "Column Name" );
        Map( m => m.Property2 ).Index( 4 );
        Map( m => m.Property3 ).Ignore();
        Map( m => m.Property4 ).TypeConverter<MySpecialTypeConverter>();
    }
}
162
Josh Close

Dans une application métier, j'utilise le projet Open Source sur codeproject.com, CSVReader .

Cela fonctionne bien et a de bonnes performances. Il existe des analyses comparatives sur le lien que j'ai fourni.

Un exemple simple, copié de la page du projet:

using (CsvReader csv = new CsvReader(new StreamReader("data.csv"), true))
{
    int fieldCount = csv.FieldCount;
    string[] headers = csv.GetFieldHeaders();

    while (csv.ReadNextRecord())
    {
        for (int i = 0; i < fieldCount; i++)
            Console.Write(string.Format("{0} = {1};", headers[i], csv[i]));

        Console.WriteLine();
    }
}

Comme vous pouvez le constater, il est très facile de travailler avec.

30
alexn

Je sais que c'est un peu tard, mais je viens de trouver une bibliothèque Microsoft.VisualBasic.FileIO qui a la classe TextFieldParser pour traiter les fichiers csv.

17
user1131926

Si vous n'avez besoin que de lire des fichiers csv, je vous recommande cette bibliothèque: Un lecteur CSV rapide .__
Si vous devez également générer des fichiers csv, utilisez celui-ci: FileHelpers

Les deux sont gratuits et opensource.

12
Giorgi

Voici une classe d'assistance que j'utilise souvent, au cas où quelqu'un reviendrait sur ce fil (je voulais le partager).

J'utilise ceci pour la simplicité de son portage dans des projets prêts à l'emploi:

public class CSVHelper : List<string[]>
{
  protected string csv = string.Empty;
  protected string separator = ",";

  public CSVHelper(string csv, string separator = "\",\"")
  {
    this.csv = csv;
    this.separator = separator;

    foreach (string line in Regex.Split(csv, System.Environment.NewLine).ToList().Where(s => !string.IsNullOrEmpty(s)))
    {
      string[] values = Regex.Split(line, separator);

      for (int i = 0; i < values.Length; i++)
      {
        //Trim values
        values[i] = values[i].Trim('\"');
      }

      this.Add(values);
    }
  }
}

Et utilisez-le comme:

public List<Person> GetPeople(string csvContent)
{
  List<Person> people = new List<Person>();
  CSVHelper csv = new CSVHelper(csvContent);
  foreach(string[] line in csv)
  {
    Person person = new Person();
    person.Name = line[0];
    person.TelephoneNo = line[1];
    people.Add(person);
  }
  return people;
}

[Mise à jour de l’assistant csv: correction d’un bogue où le dernier caractère de la nouvelle ligne créait une nouvelle ligne]

10
Base33

Cette solution utilise le fichier officiel Microsoft.VisualBasic Assembly pour analyser CSV.

Avantages:

  • délimiteur s'échappant
  • ignore l'en-tête 
  • couper les espaces
  • ignorer les commentaires

Code:

    using Microsoft.VisualBasic.FileIO;

    public static List<List<string>> ParseCSV (string csv)
    {
        List<List<string>> result = new List<List<string>>();


        // To use the TextFieldParser a reference to the Microsoft.VisualBasic Assembly has to be added to the project. 
        using (TextFieldParser parser = new TextFieldParser(new StringReader(csv))) 
        {
            parser.CommentTokens = new string[] { "#" };
            parser.SetDelimiters(new string[] { ";" });
            parser.HasFieldsEnclosedInQuotes = true;

            // Skip over header line.
            //parser.ReadLine();

            while (!parser.EndOfData)
            {
                var values = new List<string>();

                var readFields = parser.ReadFields();
                if (readFields != null)
                    values.AddRange(readFields);
                result.Add(values);
            }
        }

        return result;
    }
8
Jonas_Hess

J'ai écrit TinyCsvParser pour .NET, qui est l’un des analyseurs syntaxiques .NET les plus rapides du marché et hautement configurable pour analyser presque tous les formats CSV.

Il est publié sous la licence MIT:

Vous pouvez utiliser NuGet pour l'installer. Exécutez la commande suivante dans Package Manager Console .

PM> Install-Package TinyCsvParser

Usage

Imaginez que nous ayons une liste de personnes dans un fichier CSV persons.csv avec leur prénom, nom de famille et date de naissance.

FirstName;LastName;BirthDate
Philipp;Wagner;1986/05/12
Max;Musterman;2014/01/02

Le modèle de domaine correspondant dans notre système pourrait ressembler à ceci.

private class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

Lorsque vous utilisez TinyCsvParser, vous devez définir le mappage entre les colonnes des données CSV et la propriété de votre modèle de domaine.

private class CsvPersonMapping : CsvMapping<Person>
{

    public CsvPersonMapping()
        : base()
    {
        MapProperty(0, x => x.FirstName);
        MapProperty(1, x => x.LastName);
        MapProperty(2, x => x.BirthDate);
    }
}

Ensuite, nous pouvons utiliser le mappage pour analyser les données CSV avec un CsvParser.

namespace TinyCsvParser.Test
{
    [TestFixture]
    public class TinyCsvParserTest
    {
        [Test]
        public void TinyCsvTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvPersonMapping csvMapper = new CsvPersonMapping();
            CsvParser<Person> csvParser = new CsvParser<Person>(csvParserOptions, csvMapper);

            var result = csvParser
                .ReadFromFile(@"persons.csv", Encoding.ASCII)
                .ToList();

            Assert.AreEqual(2, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));

            Assert.AreEqual("Philipp", result[0].Result.FirstName);
            Assert.AreEqual("Wagner", result[0].Result.LastName);

            Assert.AreEqual(1986, result[0].Result.BirthDate.Year);
            Assert.AreEqual(5, result[0].Result.BirthDate.Month);
            Assert.AreEqual(12, result[0].Result.BirthDate.Day);

            Assert.AreEqual("Max", result[1].Result.FirstName);
            Assert.AreEqual("Mustermann", result[1].Result.LastName);

            Assert.AreEqual(2014, result[1].Result.BirthDate.Year);
            Assert.AreEqual(1, result[1].Result.BirthDate.Month);
            Assert.AreEqual(1, result[1].Result.BirthDate.Day);
        }
    }
}

Mode d'emploi

Un guide de l'utilisateur complet est disponible à l'adresse suivante:

7
bytefish

Je ne connais aucun moyen officiel, mais vous devriez en effet utiliser les bibliothèques existantes. En voici un que j'ai trouvé très utile chez CodeProject:

http://www.codeproject.com/KB/database/CsvReader.aspx

3
VitalyB

Solution de fichier source unique pour des besoins d'analyse simples, utile. Traite avec tous les cas méchants Edge. Telles que la normalisation de nouvelles lignes et la gestion de nouvelles lignes dans les littéraux de chaîne cités. Vous êtes le bienvenu!

Si votre fichier CSV a un en-tête, il vous suffit de lire les noms de colonne (et de calculer les index de colonne) de la première ligne. Aussi simple que cela.

Notez que Dump est une méthode LINQPad. Si vous n’utilisez pas LINQPad, vous pouvez le supprimer.

void Main()
{
    var file1 = "a,b,c\r\nx,y,z";
    CSV.ParseText(file1).Dump();

    var file2 = "a,\"b\",c\r\nx,\"y,z\"";
    CSV.ParseText(file2).Dump();

    var file3 = "a,\"b\",c\r\nx,\"y\r\nz\"";
    CSV.ParseText(file3).Dump();

    var file4 = "\"\"\"\"";
    CSV.ParseText(file4).Dump();
}

static class CSV
{
    public struct Record
    {
        public readonly string[] Row;

        public string this[int index] => Row[index];

        public Record(string[] row)
        {
            Row = row;
        }
    }

    public static List<Record> ParseText(string text)
    {
        return Parse(new StringReader(text));
    }

    public static List<Record> ParseFile(string fn)
    {
        using (var reader = File.OpenText(fn))
        {
            return Parse(reader);
        }
    }

    public static List<Record> Parse(TextReader reader)
    {
        var data = new List<Record>();

        var col = new StringBuilder();
        var row = new List<string>();
        for (; ; )
        {
            var ln = reader.ReadLine();
            if (ln == null) break;
            if (Tokenize(ln, col, row))
            {
                data.Add(new Record(row.ToArray()));
                row.Clear();
            }
        }

        return data;
    }

    public static bool Tokenize(string s, StringBuilder col, List<string> row)
    {
        int i = 0;

        if (col.Length > 0)
        {
            col.AppendLine(); // continuation

            if (!TokenizeQuote(s, ref i, col, row))
            {
                return false;
            }
        }

        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == ',')
            {
                row.Add(col.ToString().Trim());
                col.Length = 0;
                i++;
            }
            else if (ch == '"')
            {
                i++;
                if (!TokenizeQuote(s, ref i, col, row))
                {
                    return false;
                }
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }

        if (col.Length > 0)
        {
            row.Add(col.ToString().Trim());
            col.Length = 0;
        }

        return true;
    }

    public static bool TokenizeQuote(string s, ref int i, StringBuilder col, List<string> row)
    {
        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == '"')
            {
                // escape sequence
                if (i + 1 < s.Length && s[i + 1] == '"')
                {
                    col.Append('"');
                    i++;
                    i++;
                    continue;
                }
                i++;
                return true;
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }
        return false;
    }
}
1
John Leidegren

Il y a quelque temps, j'avais écrit un cours simple pour la lecture/écriture au format CSV basé sur la bibliothèque Microsoft.VisualBasic. En utilisant cette classe simple, vous pourrez travailler avec CSV comme avec un tableau à 2 dimensions. Vous pouvez trouver ma classe en cliquant sur le lien suivant: https://github.com/ukushu/DataExporter

Exemple simple d'utilisation:

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

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

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

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

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

Pour lire l'en-tête, il suffit de lire les cellules csv.Rows[0] :)

1
Andrew

Voici ma mise en œuvre KISS ...

using System;
using System.Collections.Generic;
using System.Text;

class CsvParser
{
    public static List<string> Parse(string line)
    {
        const char escapeChar = '"';
        const char splitChar = ',';
        bool inEscape = false;
        bool priorEscape = false;

        List<string> result = new List<string>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            switch (c)
            {
                case escapeChar:
                    if (!inEscape)
                        inEscape = true;
                    else
                    {
                        if (!priorEscape)
                        {
                            if (i + 1 < line.Length && line[i + 1] == escapeChar)
                                priorEscape = true;
                            else
                                inEscape = false;
                        }
                        else
                        {
                            sb.Append(c);
                            priorEscape = false;
                        }
                    }
                    break;
                case splitChar:
                    if (inEscape) //if in escape
                        sb.Append(c);
                    else
                    {
                        result.Add(sb.ToString());
                        sb.Length = 0;
                    }
                    break;
                default:
                    sb.Append(c);
                    break;
            }
        }

        if (sb.Length > 0)
            result.Add(sb.ToString());

        return result;
    }

}
1
Alex Begun

Un autre à cette liste, Cinchoo ETL - une bibliothèque open source pour lire et écrire plusieurs formats de fichiers (CSV, fichiers plats, Xml, JSON, etc.)

L'exemple ci-dessous montre comment lire rapidement un fichier CSV (aucun objet POCO requis)

string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

using (var p = ChoCSVReader.LoadText(csv)
    .WithFirstLineHeader()
    )
{
    foreach (var rec in p)
    {
        Console.WriteLine($"Id: {rec.Id}");
        Console.WriteLine($"Name: {rec.Name}");
    }
}

L'exemple ci-dessous montre comment lire un fichier CSV avec un objet POCO

public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

static void CSVTest()
{
    string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

    using (var p = ChoCSVReader<EmployeeRec>.LoadText(csv)
        .WithFirstLineHeader()
        )
    {
        foreach (var rec in p)
        {
            Console.WriteLine($"Id: {rec.Id}");
            Console.WriteLine($"Name: {rec.Name}");
        }
    }
}

S'il vous plaît consulter les articles à CodeProject sur la façon de l'utiliser.

0
RajN

Ce code lit csv sur DataTable:

public static DataTable ReadCsv(string path)
{
    DataTable result = new DataTable("SomeData");
    using (TextFieldParser parser = new TextFieldParser(path))
    {
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");
        bool isFirstRow = true;
        //IList<string> headers = new List<string>();

        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (isFirstRow)
            {
                foreach (string field in fields)
                {
                    result.Columns.Add(new DataColumn(field, typeof(string)));
                }
                isFirstRow = false;
            }
            else
            {
                int i = 0;
                DataRow row = result.NewRow();
                foreach (string field in fields)
                {
                    row[i++] = field;
                }
                result.Rows.Add(row);
            }
        }
    }
    return result;
}
0
PolinaC

Basé sur la publication de unlimit sur Comment diviser correctement un CSV en utilisant la fonction C # split ()? :

string[] tokens = System.Text.RegularExpressions.Regex.Split(paramString, ",");

REMARQUE: cela ne gère pas les virgules, etc., et ne convient donc que pour certaines listes CSV simples.

0
radsdau