Importuj plik CSV do silnie wpisanej struktury danych w .Net [zamknięte]


106

Jaki jest najlepszy sposób importowania pliku CSV do struktury danych o jednoznacznie określonym typie?




7
Biorąc pod uwagę, że został utworzony rok wcześniej niż 1103495, myślę, że to pytanie jest duplikatem tego.
MattH

2
Dzięki, Matt. Po prostu próbowałem je połączyć, nie wskazując, który z nich był pierwszy. Zobaczysz, że na drugim pytaniu mam dokładnie ten sam tekst. Czy jest lepszy sposób na powiązanie dwóch pytań?
Mark Meuer

Odpowiedzi:


74

Microsoft TextFieldParser jest stabilny i zgodny z RFC 4180 dla plików CSV. Nie zniechęcaj się Microsoft.VisualBasicprzestrzenią nazw; jest to standardowy komponent w .NET Framework, wystarczy dodać odwołanie do Microsoft.VisualBasiczestawu globalnego .

Jeśli kompilujesz dla systemu Windows (w przeciwieństwie do Mono) i nie przewidujesz konieczności analizowania „uszkodzonych” (niezgodnych z RFC) plików CSV, to byłby to oczywisty wybór, ponieważ jest darmowy, nieograniczony, stabilny, i aktywnie wspierane, o większości których nie można powiedzieć o FileHelpers.

Zobacz także: Porady: odczytywanie z plików tekstowych rozdzielanych przecinkami w języku Visual Basic, aby zapoznać się z przykładem kodu VB.


2
W rzeczywistości nie ma nic specyficznego dla języka VB w tej klasie poza przestrzenią nazw o niestety nazwanej nazwie. Zdecydowanie wybrałbym tę bibliotekę, gdybym potrzebował tylko „prostego” parsera CSV, ponieważ nie ma o co pobierać, rozpowszechniać ani się o co martwić. W tym celu zredagowałem sformułowanie skoncentrowane na VB w tej odpowiedzi.
Aaronaught

@Aaronaught Myślę, że Twoje zmiany to głównie poprawa. Chociaż ten RFC niekoniecznie jest autorytatywny, ponieważ wielu autorów CSV nie przestrzega go, np. Excel nie zawsze używa przecinka w plikach „CSV”. Czy moja poprzednia odpowiedź nie mówiła już, że klasa może być używana z C #?
MarkJ

TextFieldParserPraca będzie rozdzielany tabulatorami i inne dziwne Excel generowanego cruft też. Zdaję sobie sprawę, że Twoja poprzednia odpowiedź nie wskazywała, że ​​biblioteka jest specyficzna dla VB, po prostu przyszła mi do głowy jako sugestia, że ​​była naprawdę przeznaczona dla VB i nie jest przeznaczona do użycia z C #, co nie wydaje mi się przypadek - w MSVB jest kilka naprawdę przydatnych klas.
Aaronaught

21

Użyj połączenia 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();

Wymaga to dostępu do systemu plików. O ile wiem, nie ma sposobu, aby OLEDB działał ze strumieniami w pamięci :(
UserControl

3
@UserControl, oczywiście, wymaga dostępu do systemu plików. Zapytał o import pliku CSV
Kevin

1
Nie narzekam. W rzeczywistości wolałbym rozwiązanie OLEDB od reszty, ale byłem sfrustrowany tak wiele razy, gdy trzeba było przeanalizować CSV w aplikacjach ASP.NET, więc chciałem to zauważyć.
UserControl

12

Jeśli spodziewasz się dość złożonych scenariuszy analizowania CSV, nawet nie myśl o toczeniu naszego własnego parsera . Istnieje wiele doskonałych narzędzi, takich jak FileHelpers , a nawet te z CodeProject .

Chodzi o to, że jest to dość powszechny problem i można się założyć, że wielu programistów już przemyślało i rozwiązało ten problem.


Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie. - Z recenzji
techspider

Dzięki @techspider Mam nadzieję, że zauważyłeś, że ten post pochodzi z okresu beta StackOverflow: D To powiedziawszy, że obecnie narzędzia CSV są lepiej pozyskiwane z pakietów Nuget - więc nie jestem pewien, czy nawet odpowiedzi na linki są odporne na 8 lat -stare cykle ewolucji technologii
Jon Limjap

9

Brian daje fajne rozwiązanie do konwersji go na kolekcję silnie wpisaną.

Większość podanych metod analizy CSV nie bierze pod uwagę pól ucieczki ani niektórych innych subtelności plików CSV (takich jak pola przycinania). Oto kod, którego osobiście używam. Jest trochę szorstki na krawędziach i prawie nie ma raportowania błędów.

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

Zauważ, że nie obsługuje to skrajnych przypadków pól, które nie są oddzielone podwójnymi cudzysłowami, ale meerley ma w sobie ciąg znaków w cudzysłowie. Zobacz ten post, aby uzyskać lepsze rozszerzenie, a także kilka linków do niektórych odpowiednich bibliotek.


9

Zgadzam się z @ NotMyself . FileHelpers jest dobrze przetestowany i obsługuje wszystkie rodzaje skrajnych przypadków, z którymi w końcu będziesz musiał sobie poradzić, jeśli zrobisz to sam. Spójrz na to, co robi FileHelpers i napisz własne tylko wtedy, gdy jesteś absolutnie pewien, że albo (1) nigdy nie będziesz musiał zajmować się skrajnymi przypadkami, które robi FileHelpers, albo (2) uwielbiasz pisać tego rodzaju rzeczy i zamierzasz ciesz się, gdy musisz analizować takie rzeczy:

1, „Bill”, „Smith”, „Supervisor”, „No Comment”

2, „Drake”, „O'Malley”, „Woźny,

Ups, nie jestem cytowany i jestem na nowej linii!


6

Byłem znudzony, więc zmodyfikowałem kilka rzeczy, które napisałem. Próbuje hermetyzować parsowanie w sposób OO, jednocześnie zmniejszając liczbę iteracji w pliku, a iteruje tylko raz na początku.

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


2

Dobrym, prostym sposobem jest otwarcie pliku i wczytanie każdego wiersza do tablicy, listy połączonej, wybranej struktury danych. Uważaj jednak na pierwszą linię.

Może to być ponad twoją głową, ale wydaje się, że istnieje bezpośredni sposób na uzyskanie do nich dostępu za pomocą parametrów połączenia .

Dlaczego nie spróbować użyć Pythona zamiast C # lub VB? Ma ładny moduł CSV do zaimportowania, który wykonuje wszystkie ciężkie prace za Ciebie.


1
Nie skacz do Pythona z VB ze względu na parser CSV. Jest jeden w VB. Chociaż wydaje się dziwne, że zostało to zignorowane w odpowiedziach na to pytanie. msdn.microsoft.com/en-us/library/…
MarkJ

1

Tego lata musiałem użyć parsera CSV w .NET do projektu i zdecydowałem się na sterownik Microsoft Jet Text Driver. Określasz folder za pomocą parametrów połączenia, a następnie odpytuj plik za pomocą instrukcji SQL Select. Silne typy można określić za pomocą pliku schema.ini. Na początku tego nie zrobiłem, ale potem otrzymywałem złe wyniki, gdy typ danych nie był od razu widoczny, na przykład numery IP lub wpis typu „XYQ 3.9 SP1”.

Jedynym ograniczeniem, na które natknąłem się, jest to, że nie obsługuje nazw kolumn powyżej 64 znaków; to obcina. Nie powinno to stanowić problemu, poza tym, że miałem do czynienia z bardzo źle zaprojektowanymi danymi wejściowymi. Zwraca ADO.NET DataSet.

To było najlepsze rozwiązanie, jakie znalazłem. Byłbym ostrożny, jeśli chodzi o rozwijanie własnego parsera CSV, ponieważ prawdopodobnie przegapiłbym niektóre przypadki końcowe, a nie znalazłem żadnych innych bezpłatnych pakietów parsujących CSV dla .NET.

EDYCJA: Ponadto w każdym katalogu może istnieć tylko jeden plik schema.ini, więc dodałem do niego dynamicznie, aby silnie wpisać potrzebne kolumny. Będzie tylko silnie wpisywać określone kolumny i wywnioskować dla dowolnego nieokreślonego pola. Naprawdę to doceniam, ponieważ miałem do czynienia z importowaniem płynnego pliku CSV z kolumnami 70+ i nie chciałem określać każdej kolumny, tylko te źle działające.


Dlaczego nie VB.NET wbudowany w parser CSV? msdn.microsoft.com/en-us/library/…
MarkJ

1

Wpisałem jakiś kod. Wynik w datagridviewer wyglądał dobrze. Przetwarza pojedynczy wiersz tekstu do tablicy obiektów.

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

0

Jeśli możesz zagwarantować, że w danych nie ma przecinków, najprostszym sposobem byłoby prawdopodobnie użycie String.split .

Na przykład:

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

Mogą istnieć biblioteki, których możesz użyć, aby pomóc, ale prawdopodobnie jest to tak proste, jak tylko możesz. Po prostu upewnij się, że nie możesz mieć przecinków w danych, w przeciwnym razie będziesz musiał lepiej je przeanalizować.


to nie jest optymalne rozwiązanie
roundcrisis

bardzo złe zużycie pamięci i dużo narzutów. Małe powinno być mniejsze dzięki kilku kilobajtom. Zdecydowanie nie jest to dobre dla pliku CSV o wielkości 10 MB!
Piotr Kula

To zależy od rozmiaru twojej pamięci i pliku.
tonymiao
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.