Lire des fichiers CSV en utilisant C #

169

J'écris une application d'importation simple et j'ai besoin de lire un fichier CSV, d'afficher le résultat dans un DataGridet d'afficher les lignes corrompues du fichier CSV dans une autre grille. Par exemple, affichez les lignes plus courtes que 5 valeurs dans une autre grille. J'essaye 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 une meilleure façon de diviser les valeurs?

ilkin
la source
Merci pour votre solution. Pensez à le publier en tant que message de réponse - l'inclure dans la question n'aide pas sa lisibilité.
BartoszKP

Réponses:

363

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

  • ajouter une référence au Microsoft.VisualBasic(oui, cela dit VisualBasic mais cela fonctionne aussi bien en C # - rappelez-vous qu'à la fin, tout est juste IL)
  • utiliser la Microsoft.VisualBasic.FileIO.TextFieldParserclasse 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:

David Pokluda
la source
18
Je souhaite VRAIMENT qu'il y ait un moyen qui n'utilise pas les bibliothèques VB, mais cela a parfaitement fonctionné! Je vous remercie!
gillonba
5
+1: Je viens de casser le lecteur lumenworks Fast CSV sur un fichier de 53 Mo. On dirait que la mise en cache des lignes a échoué après 43 000 lignes et brouillé le tampon. J'ai essayé le VB TextFieldParseret ça a fait l'affaire. Merci
Fini le codage
10
+1 Excellente réponse, car je trouve que beaucoup de gens ne savent pas que cette classe existe. Une chose à noter pour les futurs téléspectateurs est que le réglage parser.TextFieldType = FieldType.Delimited;n'est pas nécessaire si vous appelez parser.SetDelimiters(",");, car la méthode définit la TextFieldTypepropriété pour vous.
Brian
10
Vérifiez également ceci: dotnetperls.com/textfieldparser . TextFieldParser a des performances moins bonnes que String.Split et StreamReader. Cependant, il existe une grande différence entre string.Split et TextFieldParser. TextFieldParser gère des cas étranges comme avoir une virgule dans une colonne: vous pouvez nommer une colonne comme "text with quote"", and comma", et vous pouvez obtenir la valeur correcte text with quote", and commaau lieu de valeurs mal séparées. Vous pouvez donc opter pour String.Split si vous csv est très simple.
Yongwei Wu
5
Notez que vous devrez peut-être ajouter une référence à Microsoft.VisualBasic pour l'utiliser. Cliquez avec le bouton droit sur votre projet dans Visual Studio, puis choisissez Ajouter> Référence et cochez la case Microsoft.VisualBasic.
Derek Kurth
37

Mon expérience est qu'il existe de nombreux formats csv différents. Surtout comment ils gèrent l'échappement des guillemets et des délimiteurs dans un champ.

Voici les variantes que j'ai rencontrées:

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

J'ai essayé de nombreux analyseurs csv existants, mais il n'y en a pas un seul qui puisse gérer les variantes que j'ai rencontrées. Il est également difficile de trouver dans la documentation quelles variantes échappantes supportées par les analyseurs.

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

adrianm
la source
1
J'adore cette réponse pour les cas de test que vous avez fournis!
Matthew Rodatus
2
Le principal problème est que la plupart des implémentations ne se soucient pas de la RFC 4180 qui décrit le format CSV et comment les délimiteurs doivent être échappés.
Jenny O'Reilly
La RFC-4180 date de 2005, ce qui semble vieux maintenant, mais rappelez-vous: le framework .Net a été lancé pour la première fois en 2001. De plus, les RFC ne sont pas toujours des normes officielles, et dans ce cas, il n'a pas le même poids que, disons , ISO-8601 ou RFC-761.
Joel Coehoorn
23

Je recommande CsvHelper de Nuget .

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

knocte
la source
2
C'est exactement aussi multiplateforme que C #.
PRMan
faux, Microsoft.VisualBasic.dll sous Linux provient de sources Mono, qui a une implémentation différente de celle de Microsoft et il y a certaines choses qui ne sont pas implémentées, par exemple: stackoverflow.com/questions/6644165/…
knocte
(De plus, le langage VB n'a jamais été axé sur les entreprises qui ont été impliquées dans la création / le développement du projet Mono, il est donc très en retard en termes d'efforts, par rapport à l'écosystème / outillage C #.)
knocte
1
Après avoir joué avec les deux, j'ajouterais que cela CsvHelpervient avec une ligne intégrée au mappeur de classe; il permet des variations dans les en-têtes de colonne (si présents), et même apparemment des variations dans l'ordre des colonnes (bien que je n'ai pas testé ce dernier moi-même). Dans l'ensemble, cela semble beaucoup plus "de haut niveau" que TextFieldParser.
David le
1
yup, l'espace de noms Microsoft.VisualBasic n'est pas disponible sur .NET Core 2.1
N4ppeL
13

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

  1. Dans cet exemple, j'utilise StreamReader pour lire le fichier
  2. Regex pour détecter le délimiteur de chaque ligne (s).
  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 */
        }
    }
Mana
la source
4
Cela a sûrement des problèmes avec les données qui contiennent elles-mêmes de nouvelles lignes?
Doogal
Maintenant, les fichiers de données CSV ne sont pas connus pour contenir des lignes vides entre les données, mais si vous avez une source qui le fait, dans ce cas, je ferais simplement un simple test regex pour supprimer les espaces ou les lignes ne contenant rien avant d'exécuter le lecteur. Consultez ici différents exemples: stackoverflow.com/questions/7647716/…
Mana
1
Une approche basée sur les caractères est sûrement plus naturelle pour ce type de problème qu'une regex. En fonction de la présence de guillemets, le comportement est censé être différent.
Casey
6

CSV peut devenir très vite compliqué .

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

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

Keith souffle
la source
5
Je pense que FileHelper essaie de faire beaucoup en une seule fois. L'analyse des fichiers est un processus en 2 étapes dans lequel vous divisez d'abord les lignes en champs, puis analysez les champs en données. La combinaison des fonctions rend difficile la gestion de tâches telles que les détails principaux et le filtrage de lignes.
adrianm
4

Un autre sur 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

Vous pouvez les charger rapidement en utilisant la bibliothèque comme 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);
   }
}

Veuillez consulter les articles de CodeProject sur la façon de l'utiliser.

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

RajN
la source
Salut, pouvez-vous charger csv dans la table Sql - Je ne connais pas l'en-tête de la table CSV à l'avance. Il suffit de refléter ce qui se trouve dans la table
CSV
Oui, vous pouvez. s'il vous plaît voir ce lien stackoverflow.com/questions/20759302/…
RajN
2
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();
                    }
                }

         }
anongf4gsdfg54564533
la source
d'où avez-vous copié cette solution?
MindRoasterMir
0

Tout d'abord, il faut comprendre ce qu'est CSV et comment l'écrire.

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

Le moyen le plus simple pour C # / Visual Basic de travailler avec des fichiers CSV consiste à utiliser une Microsoft.VisualBasicbibliothèque standard . Il vous suffit d'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 donc travailler avec tous les fichiers CSV.

Il y a quelque temps, j'avais écrit une classe simple pour la lecture / écriture CSV basée 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");
Andrew
la source
0

Pour compléter les réponses précédentes, on peut avoir besoin d'une collection d'objets de son fichier CSV, soit analysés par TextFieldParserla string.Splitméthode ou par la méthode, puis chaque ligne convertie en objet via Reflection. Vous devez évidemment 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 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;            
}
EricBDev
la source
0

Je suggère fortement d'utiliser CsvHelper.

Voici un exemple rapide:

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

La documentation complète peut être trouvée à: https://joshclose.github.io/CsvHelper

Kieran
la source