Domanda

Mi rendo conto che questa è una domanda da principiante, ma sto cercando una soluzione semplice: sembra che dovrebbe essercene una.

Qual è il modo migliore per importare un file CSV in una struttura dati fortemente tipizzata?Ancora una volta semplice = migliore.

È stato utile?

Soluzione

Altri suggerimenti

Quello di Microsoft TextFieldParser è stabile e segue RFC4180 per i file CSV.Non lasciarti scoraggiare dal Microsoft.VisualBasic spazio dei nomi;è un componente standard in .NET Framework, basta aggiungere un riferimento al global Microsoft.VisualBasic assemblaggio.

Se stai compilando per Windows (al contrario di Mono) e non prevedi di dover analizzare file CSV "rotti" (non conformi a RFC), allora questa sarebbe la scelta più ovvia, poiché è gratuita, senza restrizioni, stabile, e supportato attivamente, la maggior parte del quale non si può dire per FileHelpers.

Guarda anche: Come:Leggere da file di testo delimitati da virgole in Visual Basic per un esempio di codice VB.

Utilizza una connessione 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();

Se ti aspetti scenari abbastanza complessi per l'analisi CSV, non pensare nemmeno di lanciare il nostro parser.Ci sono molti strumenti eccellenti là fuori, come FileHelper, o anche quelli da CodiceProgetto.

Il punto è che questo è un problema abbastanza comune e puoi scommetterci molto degli sviluppatori di software hanno già pensato e risolto questo problema.

Brian fornisce una bella soluzione per convertirlo in una raccolta fortemente tipizzata.

La maggior parte dei metodi di analisi CSV forniti non tengono conto dei campi di escape o di alcune altre sottigliezze dei file CSV (come il taglio dei campi).Ecco il codice che utilizzo personalmente.È un po' approssimativo e non presenta praticamente alcuna segnalazione di errori.

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

Si noti che questo non gestisce il caso limite dei campi non delimitati da virgolette doppie, ma con una stringa tra virgolette al suo interno.Vedere questo post per una migliore espansione e alcuni collegamenti ad alcune librerie adeguate.

Sono d'accordo con @Non me stesso. FileHelper è ben testato e gestisce tutti i tipi di casi limite che alla fine dovrai affrontare se lo fai da solo.Dai un'occhiata a cosa fa FileHelpers e scrivi il tuo solo se sei assolutamente sicuro che (1) non avrai mai bisogno di gestire i casi limite che FileHelpers fa, o (2) ami scrivere questo tipo di cose e lo farai sii felicissimo quando devi analizzare cose come queste:

1,"Fattura","Smith","Supervisore", "Nessun commento"

2, 'Drake', 'O'Malley', "Portiere,

Ops, non sono citato e sono su una nuova riga!

Mi annoiavo, quindi ho modificato alcune cose che avevo scritto.Cerca di incapsulare l'analisi in modo OO riducendo la quantità di iterazioni attraverso il file, itera solo una volta in alto per ciascuno.

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

Sono disponibili due articoli su CodeProject che forniscono il codice per una soluzione, una che utilizza StreamReader e uno quello importa dati CSV usando il Driver di testo Microsoft.

Un buon modo semplice per farlo è aprire il file e leggere ogni riga in un array, un elenco collegato, una struttura dati di tua scelta.Fai attenzione però a gestire la prima riga.

Questo potrebbe essere difficile per te, ma sembra che ci sia un modo diretto per accedervi anche utilizzando un file stringa di connessione.

Perché non provare a utilizzare Python anziché C# o VB?Ha un bel modulo CSV da importare che fa tutto il lavoro pesante per te.

Quest'estate ho dovuto utilizzare un parser CSV in .NET per un progetto e ho optato per Microsoft Jet Text Driver.Si specifica una cartella utilizzando una stringa di connessione, quindi si esegue una query su un file utilizzando un'istruzione SQL Select.È possibile specificare tipi complessi utilizzando un file schema.ini.All'inizio non l'ho fatto, ma poi ho ottenuto risultati scadenti in cui il tipo di dati non era immediatamente evidente, come numeri IP o una voce come "XYQ 3.9 SP1".

Una limitazione che ho riscontrato è che non può gestire nomi di colonne superiori a 64 caratteri;tronca.Questo non dovrebbe essere un problema, tranne per il fatto che avevo a che fare con dati di input progettati molto male.Restituisce un set di dati ADO.NET.

Questa è stata la soluzione migliore che ho trovato.Sarei cauto nel lanciare il mio parser CSV, poiché probabilmente mi perderei alcuni dei casi finali e non ho trovato nessun altro pacchetto di analisi CSV gratuito per .NET là fuori.

MODIFICARE:Inoltre, può esserci un solo file schema.ini per directory, quindi l'ho aggiunto dinamicamente per digitare in modo sicuro le colonne necessarie.Digiterà in modo forte solo le colonne specificate e dedurrà per qualsiasi campo non specificato.L'ho apprezzato molto, poiché avevo a che fare con l'importazione di un CSV fluido di oltre 70 colonne e non volevo specificare ciascuna colonna, solo quelle che si comportavano male.

Ho digitato del codice.Il risultato nel datagridviewer sembrava buono.Analizza una singola riga di testo in un arraylist di oggetti.

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

Se puoi garantire che non ci siano virgole nei dati, probabilmente il modo più semplice sarebbe utilizzare String.split.

Per esempio:

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

Potrebbero esserci librerie che potresti usare per aiutarti, ma probabilmente è il più semplice possibile.Assicurati solo di non contenere virgole nei dati, altrimenti dovrai analizzarli meglio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top