web-dev-qa-db-fra.com

Lecture de fichiers CSV en C #

J'écris une simple application d'importation et je dois lire un fichier CSV, afficher le résultat dans une variable DataGrid et afficher les lignes corrompues du fichier CSV dans une autre grille. Par exemple, affichez les lignes inférieures à 5 valeurs dans une autre grille. J'essaie de faire ça comme ça:

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

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

mais il est très difficile d'opérer sur des tableaux dans ce cas. Existe-t-il un meilleur moyen de diviser les valeurs?

144
ilkin

Ne réinventez pas la roue. Tirez parti de ce qui est déjà dans .NET BCL. 

  • ajoutez une référence au Microsoft.VisualBasic (oui, VisualBasic mais cela fonctionne aussi bien en C # - rappelez-vous qu'à la fin, il s'agit uniquement de IL)
  • utilisez la classe Microsoft.VisualBasic.FileIO.TextFieldParser pour analyser le fichier CSV

Voici l exemple de code:

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

Cela fonctionne très bien pour moi dans mes projets C #. 

Voici quelques liens/informations supplémentaires:

326
David Pokluda

Mon expérience est qu'il y a beaucoup de formats différents de csv. En particulier, comment ils gèrent l'échappement des guillemets et des délimiteurs dans un champ.

Ce sont les variantes que j'ai rencontrées:

  • les guillemets sont cités et doublés (Excel), c'est-à-dire 15 "-> field1," 15 "" ", field3
  • les citations ne sont pas modifiées à moins que le champ ne soit cité pour une autre raison. c'est-à-dire 15 "-> field1,15", fields3
  • les guillemets sont échappés avec \. c'est-à-dire 15 "-> field1," 15\"", field3
  • les guillemets ne sont pas du tout modifiés (il n'est pas toujours possible d'analyser correctement)
  • le délimiteur est cité (Excel). c'est-à-dire a, b -> champ1, "a, b", champ3
  • le délimiteur est échappé avec \. c'est-à-dire a, b -> champ1, a \, b, champ3

J'ai essayé beaucoup d'analyseurs syntaxiques csv existants, mais il n'y en a pas un qui puisse gérer les variantes que j'ai rencontrées. Il est également difficile de savoir à partir de la documentation quelles variantes d'échappement sont prises en charge par les analyseurs.

Dans mes projets, j'utilise maintenant le TextFieldParser VB ou un séparateur personnalisé.

31
adrianm

Je recommande CsvHelper de Nuget .

(L'ajout d'une référence à Microsoft.VisualBasic ne vous semble pas juste, ce n'est pas seulement moche, ce n'est probablement même pas multiplateforme.)

19
knocte

Parfois, utiliser des bibliothèques est cool quand on ne veut pas réinventer la roue, mais dans ce cas, on peut faire le même travail avec moins de lignes de code et être plus facile à lire comparé à l’utilisation de bibliothèques ..__ Voici une approche différente que je trouve très facile à utiliser.

  1. Dans cet exemple, j'utilise StreamReader pour lire le fichier.
  2. Regex pour détecter le délimiteur de chaque ligne.
  3. Un tableau pour collecter les colonnes de l'index 0 à n

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

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

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

            /* Do something with X */
        }
    }
10
Mana

Le format CSV peut être compliqué rapidement réel.

Utilisez quelque chose de robuste et bien testé:
FileHelpers: www.filehelpers.net

FileHelpers est une bibliothèque .NET gratuite et facile à utiliser pour importer/exporter des données d’enregistrements de longueur fixe ou délimités dans des fichiers, des chaînes ou des flux .

5
Keith Blows

J'utilise ceci ici:

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

La dernière fois que je cherchais quelque chose comme cela, je l’ai trouvée en réponse à cette question question .

3
Stefan Egli

Un autre à cette liste, Cinchoo ETL - une bibliothèque open source pour lire et écrire des fichiers CSV

Pour un exemple de fichier CSV ci-dessous

Id, Name
1, Tom
2, Mark

Rapidement, vous pouvez les charger en utilisant la bibliothèque ci-dessous

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

Si vous avez une classe POCO correspondant au fichier CSV

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

Vous pouvez l'utiliser pour charger le fichier CSV comme ci-dessous

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

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

Disclaimer: je suis l'auteur de cette bibliothèque

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

            }

            return dt;
        }

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

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

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

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

         }
1

Pour compléter les réponses précédentes, vous pouvez avoir besoin d’une collection d’objets de son fichier CSV, analysés par la méthode TextFieldParser ou string.Split, puis chaque ligne convertie en objet par Reflection. Évidemment, vous devez d’abord définir une classe qui correspond aux lignes du fichier CSV.

J'ai utilisé le simple sérialiseur CSV de Michael Kropat trouvé ici: Classe générique en CSV (toutes les propriétés) Et ai réutilisé ses méthodes pour obtenir les champs et les propriétés de la classe souhaitée.

Je désérialise mon fichier CSV avec la méthode suivante:

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

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

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

        bool headerParsed = false;

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

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

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

Tout d'abord besoin de comprendre ce qu'est le CSV et comment l'écrire.

  1. Chaque chaîne suivante (/r/n) est la prochaine ligne "table".
  2. Les cellules "Table" sont séparées par un symbole de délimiteur. Le symbole le plus souvent utilisé est \t ou ,
  3. Chaque cellule peut éventuellement contenir ce symbole délimiteur (la cellule doit commencer par un symbole entre guillemets et se termine par ce symbole dans ce cas).
  4. Chaque cellule peut éventuellement contenir /r/n sybols (la cellule doit commencer par un symbole entre guillemets et se termine par ce symbole dans ce cas).

Pour que C #/Visual Basic fonctionne avec des fichiers CSV, le moyen le plus simple consiste à utiliser la bibliothèque standard Microsoft.VisualBasic. Vous devez juste ajouter la référence nécessaire et la chaîne suivante à votre classe:

using Microsoft.VisualBasic.FileIO;

Oui, vous pouvez l'utiliser en C #, ne vous inquiétez pas. Cette bibliothèque peut lire des fichiers relativement volumineux et prend en charge toutes les règles nécessaires. Vous pourrez ainsi travailler avec tous les fichiers CSV.

Il y a quelque temps, j'avais écrit un cours simple pour la lecture/écriture au format CSV basé sur cette bibliothèque. En utilisant cette classe simple, vous pourrez travailler avec CSV comme avec un tableau à 2 dimensions. Vous pouvez trouver ma classe par 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");
0
Andrew