Question

Je me rends compte que c'est une question de débutant, mais je cherche une solution simple – il semble qu'il devrait y en avoir une.

Quelle est la meilleure façon d'importer un fichier CSV dans une structure de données fortement typée ?Encore une fois simple = mieux.

Était-ce utile?

La solution

Autres conseils

de Microsoft TextFieldParser est stable et suit RFC4180 pour les fichiers CSV.Ne vous laissez pas rebuter par le Microsoft.VisualBasic espace de noms ;c'est un composant standard dans le .NET Framework, ajoutez simplement une référence au global Microsoft.VisualBasic assemblée.

Si vous compilez pour Windows (par opposition à Mono) et que vous ne prévoyez pas d'avoir à analyser des fichiers CSV « cassés » (non conformes à la RFC), alors ce serait le choix évident, car il est gratuit, sans restriction, stable, et activement pris en charge, dont la plupart ne peuvent pas être dits pour FileHelpers.

Voir également: Comment:Lire à partir de fichiers texte délimités par des virgules dans Visual Basic pour un exemple de code VB.

Utilisez une connexion OleDB.

String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();

Si vous vous attendez à des scénarios assez complexes pour l'analyse CSV, ne pensez même pas à lancer notre propre analyseur.Il existe de nombreux excellents outils, comme Aides-fichiers, ou même ceux de CodeProjet.

Le fait est qu'il s'agit d'un problème assez courant et vous pouvez parier que beaucoup des développeurs de logiciels ont déjà réfléchi et résolu ce problème.

Brian donne une bonne solution pour la convertir en une collection fortement typée.

La plupart des méthodes d'analyse CSV proposées ne prennent pas en compte les champs d'échappement ou certaines autres subtilités des fichiers CSV (comme le découpage des champs).Voici le code que j'utilise personnellement.C'est un peu approximatif sur les bords et il n'y a pratiquement aucun rapport d'erreurs.

public static IList<IList<string>> Parse(string content)
{
    IList<IList<string>> records = new List<IList<string>>();

    StringReader stringReader = new StringReader(content);

    bool inQoutedString = false;
    IList<string> record = new List<string>();
    StringBuilder fieldBuilder = new StringBuilder();
    while (stringReader.Peek() != -1)
    {
        char readChar = (char)stringReader.Read();

        if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
        {
            // If it's a \r\n combo consume the \n part and throw it away.
            if (readChar == '\r')
            {
                stringReader.Read();
            }

            if (inQoutedString)
            {
                if (readChar == '\r')
                {
                    fieldBuilder.Append('\r');
                }
                fieldBuilder.Append('\n');
            }
            else
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();

                records.Add(record);
                record = new List<string>();

                inQoutedString = false;
            }
        }
        else if (fieldBuilder.Length == 0 && !inQoutedString)
        {
            if (char.IsWhiteSpace(readChar))
            {
                // Ignore leading whitespace
            }
            else if (readChar == '"')
            {
                inQoutedString = true;
            }
            else if (readChar == ',')
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();
            }
            else
            {
                fieldBuilder.Append(readChar);
            }
        }
        else if (readChar == ',')
        {
            if (inQoutedString)
            {
                fieldBuilder.Append(',');
            }
            else
            {
                record.Add(fieldBuilder.ToString().TrimEnd());
                fieldBuilder = new StringBuilder();
            }
        }
        else if (readChar == '"')
        {
            if (inQoutedString)
            {
                if (stringReader.Peek() == '"')
                {
                    stringReader.Read();
                    fieldBuilder.Append('"');
                }
                else
                {
                    inQoutedString = false;
                }
            }
            else
            {
                fieldBuilder.Append(readChar);
            }
        }
        else
        {
            fieldBuilder.Append(readChar);
        }
    }
    record.Add(fieldBuilder.ToString().TrimEnd());
    records.Add(record);

    return records;
}

Notez que cela ne gère pas le cas limite des champs qui ne sont pas délimités par des guillemets doubles, mais qui contiennent une chaîne entre guillemets.Voir ce post pour une meilleure explication ainsi que quelques liens vers des bibliothèques appropriées.

Je suis d'accord avec @Pas moi. Aides-fichiers est bien testé et gère toutes sortes de cas extrêmes que vous devrez éventuellement gérer si vous le faites vous-même.Jetez un œil à ce que fait FileHelpers et n'écrivez le vôtre que si vous êtes absolument sûr que soit (1) vous n'aurez jamais besoin de gérer les cas extrêmes que FileHelpers fait, ou (2) vous aimez écrire ce genre de choses et allez le faire. soyez ravi lorsque vous devez analyser des choses comme ceci :

1, "Bill", "Smith", "Superviseur", "Pas de commentaire"

2, 'Drake', 'O'Malley', "Concierge,

Oups, je ne suis pas cité et je suis sur une nouvelle ligne !

Je m'ennuyais alors j'ai modifié certains trucs que j'avais écrit.Il essaie d'encapsuler l'analyse de manière OO tout en réduisant le nombre d'itérations dans le fichier, il n'itère qu'une seule fois en haut pour chaque.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

namespace ConsoleApplication1
{
    class Program
    {

        static void Main(string[] args)
        {

            // usage:

            // note this wont run as getting streams is not Implemented

            // but will get you started

            CSVFileParser fileParser = new CSVFileParser();

            // TO Do:  configure fileparser

            PersonParser personParser = new PersonParser(fileParser);

            List<Person> persons = new List<Person>();
            // if the file is large and there is a good way to limit
            // without having to reparse the whole file you can use a 
            // linq query if you desire
            foreach (Person person in personParser.GetPersons())
            {
                persons.Add(person);
            }

            // now we have a list of Person objects
        }
    }

    public abstract  class CSVParser 
    {

        protected String[] deliniators = { "," };

        protected internal IEnumerable<String[]> GetRecords()
        {

            Stream stream = GetStream();
            StreamReader reader = new StreamReader(stream);

            String[] aRecord;
            while (!reader.EndOfStream)
            {
                  aRecord = reader.ReadLine().Split(deliniators,
                   StringSplitOptions.None);

                yield return aRecord;
            }

        }

        protected abstract Stream GetStream(); 

    }

    public class CSVFileParser : CSVParser
    {
        // to do: add logic to get a stream from a file

        protected override Stream GetStream()
        {
            throw new NotImplementedException();
        } 
    }

    public class CSVWebParser : CSVParser
    {
        // to do: add logic to get a stream from a web request

        protected override Stream GetStream()
        {
            throw new NotImplementedException();
        }
    }

    public class Person
    {
        public String Name { get; set; }
        public String Address { get; set; }
        public DateTime DOB { get; set; }
    }

    public class PersonParser 
    {

        public PersonParser(CSVParser parser)
        {
            this.Parser = parser;
        }

        public CSVParser Parser { get; set; }

        public  IEnumerable<Person> GetPersons()
        {
            foreach (String[] record in this.Parser.GetRecords())
            {
                yield return new Person()
                {
                    Name = record[0],
                    Address = record[1],
                    DOB = DateTime.Parse(record[2]),
                };
            }
        }
    }
}

Il existe deux articles sur CodeProject qui fournissent du code pour une solution, l'un qui utilise Lecteur de flux et un qui importe des données CSV en utilisant le Pilote de texte Microsoft.

Un bon moyen simple de le faire est d'ouvrir le fichier et de lire chaque ligne dans un tableau, une liste chaînée, une structure de données de votre choix.Soyez cependant prudent lorsque vous manipulez la première ligne.

Cela vous dépasse peut-être, mais il semble exister un moyen direct d'y accéder également en utilisant un chaîne de connexion.

Pourquoi ne pas essayer d'utiliser Python au lieu de C# ou VB ?Il dispose d’un joli module CSV à importer qui fait tout le gros du travail à votre place.

J'ai dû utiliser un analyseur CSV dans .NET pour un projet cet été et j'ai opté pour le pilote Microsoft Jet Text.Vous spécifiez un dossier à l'aide d'une chaîne de connexion, puis interrogez un fichier à l'aide d'une instruction SQL Select.Vous pouvez spécifier des types forts à l'aide d'un fichier schema.ini.Je ne l'ai pas fait au début, mais j'obtenais ensuite de mauvais résultats où le type de données n'était pas immédiatement apparent, comme les numéros IP ou une entrée comme "XYQ 3.9 SP1".

Une limitation que j'ai rencontrée est qu'il ne peut pas gérer les noms de colonnes de plus de 64 caractères ;ça tronque.Cela ne devrait pas poser de problème, sauf que j'avais affaire à des données d'entrée très mal conçues.Il renvoie un DataSet ADO.NET.

C'est la meilleure solution que j'ai trouvée.Je me méfierais de lancer mon propre analyseur CSV, car je manquerais probablement certains des cas finaux et je n'ai trouvé aucun autre package d'analyse CSV gratuit pour .NET.

MODIFIER:De plus, il ne peut y avoir qu'un seul fichier schema.ini par répertoire, j'y ai donc ajouté dynamiquement pour taper fortement les colonnes nécessaires.Il ne tapera fortement que les colonnes spécifiées et déduira tout champ non spécifié.J'ai vraiment apprécié cela, car j'avais affaire à l'importation d'un CSV fluide de plus de 70 colonnes et je ne voulais pas spécifier chaque colonne, seulement celles qui se comportaient mal.

J'ai tapé du code.Le résultat dans le datagridviewer semblait bon.Il analyse une seule ligne de texte en une liste d'objets.

    enum quotestatus
    {
        none,
        firstquote,
        secondquote
    }
    public static System.Collections.ArrayList Parse(string line,string delimiter)
    {        
        System.Collections.ArrayList ar = new System.Collections.ArrayList();
        StringBuilder field = new StringBuilder();
        quotestatus status = quotestatus.none;
        foreach (char ch in line.ToCharArray())
        {                                
            string chOmsch = "char";
            if (ch == Convert.ToChar(delimiter))
            {
                if (status== quotestatus.firstquote)
                {
                    chOmsch = "char";
                }                         
                else
                {
                    chOmsch = "delimiter";                    
                }                    
            }

            if (ch == Convert.ToChar(34))
            {
                chOmsch = "quotes";           
                if (status == quotestatus.firstquote)
                {
                    status = quotestatus.secondquote;
                }
                if (status == quotestatus.none )
                {
                    status = quotestatus.firstquote;
                }
            }

            switch (chOmsch)
            {
                case "char":
                    field.Append(ch);
                    break;
                case "delimiter":                        
                    ar.Add(field.ToString());
                    field.Clear();
                    break;
                case "quotes":
                    if (status==quotestatus.firstquote)
                    {
                        field.Clear();                            
                    }
                    if (status== quotestatus.secondquote)
                    {                                                                           
                            status =quotestatus.none;                                
                    }                    
                    break;
            }
        }
        if (field.Length != 0)            
        {
            ar.Add(field.ToString());                
        }           
        return ar;
    }

Si vous pouvez garantir qu'il n'y a pas de virgules dans les données, le moyen le plus simple serait probablement d'utiliser Chaîne.split.

Par exemple:

String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);

Il existe peut-être des bibliothèques que vous pourriez utiliser pour vous aider, mais c'est probablement aussi simple que possible.Assurez-vous simplement que vous ne pouvez pas avoir de virgules dans les données, sinon vous devrez mieux les analyser.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top