Parsowanie plików CSV w C#, z nagłówkiem

Czy istnieje domyślny / oficjalny / zalecany sposób parsowania plików CSV w C#? Nie chcę obracać własnego parsera.

Widziałem również przypadki osób używających ODBC / OLE DB do odczytu CSV za pomocą sterownika tekstowego i wiele osób zniechęca to ze względu na jego "wady."Jakie są te wady?

Idealnie, szukam sposobu, przez który mogę odczytać plik CSV według nazwy kolumny, używając pierwszego rekordu jako nazwy nagłówka / pól. Niektóre z udzielonych odpowiedzi są poprawne, ale działają na zasadniczo deserializuj plik na klasy.

Author: smci, 2010-01-17

16 answers

Niech biblioteka zajmie się wszystkimi szczegółami dla Ciebie! :-)

Sprawdź FileHelpers i pozostań suchy - nie powtarzaj się - nie musisz ponownie wymyślać koła po raz milionowy....

W zasadzie wystarczy zdefiniować ten kształt danych-pola w indywidualnej linii w pliku CSV - za pomocą klasy publicznej (i tak dobrze przemyślanych atrybutów, takich jak wartości domyślne, zamienniki dla wartości NULL i tak dalej), skierować Silnik FileHelpers na plik i bingo-odzyskasz wszystkie wpisy z tego pliku. Jedna prosta obsługa-świetna wydajność!

 117
Author: marc_s,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-23 04:34:23

Parser CSV jest teraz częścią. NET Framework.

Dodaj odniesienie do Microsoft.VisualBasic.dll (działa dobrze w C#, nie przejmuj się nazwą)

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

Dokumenty są tutaj - Klasa TextFieldParser

 273
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 17:59:06

CsvHelper (biblioteka, którą zarządzam) odczyta plik CSV do obiektów niestandardowych.

var csv = new CsvReader( File.OpenText( "file.csv" ) );
var myCustomObjects = csv.GetRecords<MyCustomObject>();

Czasami nie posiadasz przedmiotów, które próbujesz odczytać. W tym przypadku możesz użyć mapowania płynnego, ponieważ nie możesz umieścić atrybutów NA klasie.

public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject>
{
    public MyCustomObjectMap()
    {
        Map( m => m.Property1 ).Name( "Column Name" );
        Map( m => m.Property2 ).Index( 4 );
        Map( m => m.Property3 ).Ignore();
        Map( m => m.Property4 ).TypeConverter<MySpecialTypeConverter>();
    }
}
 155
Author: Josh Close,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-30 03:22:07

W aplikacji biznesowej korzystam z projektu Open Source na codeproject.com, CSVReader .

Działa dobrze i ma dobrą wydajność. Jest jakiś benchmarking na link podałem.

Prosty przykład, skopiowany ze strony projektu:

using (CsvReader csv = new CsvReader(new StreamReader("data.csv"), true))
{
    int fieldCount = csv.FieldCount;
    string[] headers = csv.GetFieldHeaders();

    while (csv.ReadNextRecord())
    {
        for (int i = 0; i < fieldCount; i++)
            Console.Write(string.Format("{0} = {1};", headers[i], csv[i]));

        Console.WriteLine();
    }
}

Jak widzisz, praca z nim jest bardzo łatwa.

 30
Author: alexn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-17 15:24:38

Wiem, że jest trochę późno, ale właśnie znalazłem bibliotekę Microsoft.VisualBasic.FileIO, która ma TextFieldParser klasę do przetwarzania plików csv.

 17
Author: user1131926,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-11 20:34:42

Jeśli potrzebujesz tylko czytać pliki csv to polecam tą bibliotekę: Szybki czytnik CSV
Jeśli potrzebujesz również wygenerować pliki csv, użyj tego: FileHelpers

Oba są wolne i opensource.

 11
Author: Giorgi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-22 23:22:45

Oto Klasa pomocnicza, której często używam, na wypadek, gdyby ktoś wrócił do tego wątku(chciałem się nim podzielić).

Używam tego dla prostoty przenoszenia go do projektów gotowych do użycia:

public class CSVHelper : List<string[]>
{
  protected string csv = string.Empty;
  protected string separator = ",";

  public CSVHelper(string csv, string separator = "\",\"")
  {
    this.csv = csv;
    this.separator = separator;

    foreach (string line in Regex.Split(csv, System.Environment.NewLine).ToList().Where(s => !string.IsNullOrEmpty(s)))
    {
      string[] values = Regex.Split(line, separator);

      for (int i = 0; i < values.Length; i++)
      {
        //Trim values
        values[i] = values[i].Trim('\"');
      }

      this.Add(values);
    }
  }
}

I używaj go jak:

public List<Person> GetPeople(string csvContent)
{
  List<Person> people = new List<Person>();
  CSVHelper csv = new CSVHelper(csvContent);
  foreach(string[] line in csv)
  {
    Person person = new Person();
    person.Name = line[0];
    person.TelephoneNo = line[1];
    people.Add(person);
  }
  return people;
}

[Zaktualizowano helper csv: Naprawiono błąd, w którym ostatni znak nowej linii stworzył nową linię]

 9
Author: Base33,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-10 13:43:39

To rozwiązanie wykorzystuje oficjalny Microsoft.VisualBasic assembly to parse CSV.

Zalety:

  • delimiter escaping
  • ignoruje nagłówek
  • trim spaces
  • ignoruj komentarze

Kod:

    using Microsoft.VisualBasic.FileIO;

    public static List<List<string>> ParseCSV (string csv)
    {
        List<List<string>> result = new List<List<string>>();


        // To use the TextFieldParser a reference to the Microsoft.VisualBasic assembly has to be added to the project. 
        using (TextFieldParser parser = new TextFieldParser(new StringReader(csv))) 
        {
            parser.CommentTokens = new string[] { "#" };
            parser.SetDelimiters(new string[] { ";" });
            parser.HasFieldsEnclosedInQuotes = true;

            // Skip over header line.
            //parser.ReadLine();

            while (!parser.EndOfData)
            {
                var values = new List<string>();

                var readFields = parser.ReadFields();
                if (readFields != null)
                    values.AddRange(readFields);
                result.Add(values);
            }
        }

        return result;
    }
 8
Author: Jonas_Hess,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 12:48:02

Napisałem TinyCsvParser dla.NET, który jest jednym z najszybszych parserów. NET i wysoce konfigurowalny do analizy prawie każdego formatu CSV.

Jest wydany na licencji MIT:

Możesz użyć NuGet, aby go zainstalować. Uruchom następujące polecenie w konsoli Menedżer pakietów .

PM> Install-Package TinyCsvParser

Użycie

Wyobraź sobie, że mamy listę osób w pliku CSV persons.csv z imieniem, nazwiskiem i datą urodzenia.

FirstName;LastName;BirthDate
Philipp;Wagner;1986/05/12
Max;Musterman;2014/01/02

Odpowiedni model domeny w naszym systemie może wyglądać tak.

private class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

Podczas korzystania z TinyCsvParser musisz zdefiniować mapowanie między kolumnami w danych CSV a właściwością w twoim modelu domeny.

private class CsvPersonMapping : CsvMapping<Person>
{

    public CsvPersonMapping()
        : base()
    {
        MapProperty(0, x => x.FirstName);
        MapProperty(1, x => x.LastName);
        MapProperty(2, x => x.BirthDate);
    }
}

A następnie możemy użyć mapowania do analizy danych CSV za pomocą CsvParser.

namespace TinyCsvParser.Test
{
    [TestFixture]
    public class TinyCsvParserTest
    {
        [Test]
        public void TinyCsvTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvPersonMapping csvMapper = new CsvPersonMapping();
            CsvParser<Person> csvParser = new CsvParser<Person>(csvParserOptions, csvMapper);

            var result = csvParser
                .ReadFromFile(@"persons.csv", Encoding.ASCII)
                .ToList();

            Assert.AreEqual(2, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));

            Assert.AreEqual("Philipp", result[0].Result.FirstName);
            Assert.AreEqual("Wagner", result[0].Result.LastName);

            Assert.AreEqual(1986, result[0].Result.BirthDate.Year);
            Assert.AreEqual(5, result[0].Result.BirthDate.Month);
            Assert.AreEqual(12, result[0].Result.BirthDate.Day);

            Assert.AreEqual("Max", result[1].Result.FirstName);
            Assert.AreEqual("Mustermann", result[1].Result.LastName);

            Assert.AreEqual(2014, result[1].Result.BirthDate.Year);
            Assert.AreEqual(1, result[1].Result.BirthDate.Month);
            Assert.AreEqual(1, result[1].Result.BirthDate.Day);
        }
    }
}

Instrukcja Obsługi

Pełny podręcznik użytkownika jest dostępny at:

 7
Author: bytefish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-31 10:19:02

Nie znam oficjalnego sposobu, ale rzeczywiście powinieneś używać istniejących bibliotek. Oto jeden, który uważam za bardzo przydatny z CodeProject:

Http://www.codeproject.com/KB/database/CsvReader.aspx

 3
Author: VitalyB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-17 15:24:08

Oto mój pocałunek...

using System;
using System.Collections.Generic;
using System.Text;

class CsvParser
{
    public static List<string> Parse(string line)
    {
        const char escapeChar = '"';
        const char splitChar = ',';
        bool inEscape = false;
        bool priorEscape = false;

        List<string> result = new List<string>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            switch (c)
            {
                case escapeChar:
                    if (!inEscape)
                        inEscape = true;
                    else
                    {
                        if (!priorEscape)
                        {
                            if (i + 1 < line.Length && line[i + 1] == escapeChar)
                                priorEscape = true;
                            else
                                inEscape = false;
                        }
                        else
                        {
                            sb.Append(c);
                            priorEscape = false;
                        }
                    }
                    break;
                case splitChar:
                    if (inEscape) //if in escape
                        sb.Append(c);
                    else
                    {
                        result.Add(sb.ToString());
                        sb.Length = 0;
                    }
                    break;
                default:
                    sb.Append(c);
                    break;
            }
        }

        if (sb.Length > 0)
            result.Add(sb.ToString());

        return result;
    }

}
 1
Author: Alex Begun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-20 20:27:57

Jakiś czas temu napisałem prostą klasę do odczytu/zapisu CSV opartą na bibliotece Microsoft.VisualBasic. Używając tej prostej klasy będziesz mógł pracować z CSV jak z tablicą 2 dimensions. Możesz znaleźć moją klasę pod następującym linkiem: https://github.com/ukushu/DataExporter

Prosty przykład użycia:

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");

Do odczytu nagłówka wystarczy tylko odczytać csv.Rows[0] komórki:)

 1
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-01 04:17:49

Pojedyncze źródło pliku rozwiązanie dla prostych potrzeb parsowania, przydatne. Zajmuje się wszystkimi paskudnymi przypadkami. Takie jak normalizacja nowej linii i obsługa nowych linii w cytowanych literałach łańcuchowych. Zapraszamy!

Jeśli plik CSV ma nagłówek, po prostu odczytujesz nazwy kolumn (i obliczasz indeksy kolumn) z pierwszego wiersza. To proste.

Zauważ, że {[1] } jest metodą LINQPad, możesz chcieć ją usunąć, jeśli nie używasz Linqpada.

void Main()
{
    var file1 = "a,b,c\r\nx,y,z";
    CSV.ParseText(file1).Dump();

    var file2 = "a,\"b\",c\r\nx,\"y,z\"";
    CSV.ParseText(file2).Dump();

    var file3 = "a,\"b\",c\r\nx,\"y\r\nz\"";
    CSV.ParseText(file3).Dump();

    var file4 = "\"\"\"\"";
    CSV.ParseText(file4).Dump();
}

static class CSV
{
    public struct Record
    {
        public readonly string[] Row;

        public string this[int index] => Row[index];

        public Record(string[] row)
        {
            Row = row;
        }
    }

    public static List<Record> ParseText(string text)
    {
        return Parse(new StringReader(text));
    }

    public static List<Record> ParseFile(string fn)
    {
        using (var reader = File.OpenText(fn))
        {
            return Parse(reader);
        }
    }

    public static List<Record> Parse(TextReader reader)
    {
        var data = new List<Record>();

        var col = new StringBuilder();
        var row = new List<string>();
        for (; ; )
        {
            var ln = reader.ReadLine();
            if (ln == null) break;
            if (Tokenize(ln, col, row))
            {
                data.Add(new Record(row.ToArray()));
                row.Clear();
            }
        }

        return data;
    }

    public static bool Tokenize(string s, StringBuilder col, List<string> row)
    {
        int i = 0;

        if (col.Length > 0)
        {
            col.AppendLine(); // continuation

            if (!TokenizeQuote(s, ref i, col, row))
            {
                return false;
            }
        }

        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == ',')
            {
                row.Add(col.ToString().Trim());
                col.Length = 0;
                i++;
            }
            else if (ch == '"')
            {
                i++;
                if (!TokenizeQuote(s, ref i, col, row))
                {
                    return false;
                }
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }

        if (col.Length > 0)
        {
            row.Add(col.ToString().Trim());
            col.Length = 0;
        }

        return true;
    }

    public static bool TokenizeQuote(string s, ref int i, StringBuilder col, List<string> row)
    {
        while (i < s.Length)
        {
            var ch = s[i];
            if (ch == '"')
            {
                // escape sequence
                if (i + 1 < s.Length && s[i + 1] == '"')
                {
                    col.Append('"');
                    i++;
                    i++;
                    continue;
                }
                i++;
                return true;
            }
            else
            {
                col.Append(ch);
                i++;
            }
        }
        return false;
    }
}
 1
Author: John Leidegren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-20 13:52:40

Na podstawie postu Jak poprawnie podzielić plik CSV za pomocą funkcji C# split ()? :

string[] tokens = System.Text.RegularExpressions.Regex.Split(paramString, ",");

Notatka: nie obsługuje przecinków zawierających znaki ucieczki / zagnieżdżone itp., i dlatego nadaje się tylko do pewnych prostych list CSV.

 0
Author: radsdau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Ten kod odczytuje csv do DataTable:

public static DataTable ReadCsv(string path)
{
    DataTable result = new DataTable("SomeData");
    using (TextFieldParser parser = new TextFieldParser(path))
    {
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");
        bool isFirstRow = true;
        //IList<string> headers = new List<string>();

        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (isFirstRow)
            {
                foreach (string field in fields)
                {
                    result.Columns.Add(new DataColumn(field, typeof(string)));
                }
                isFirstRow = false;
            }
            else
            {
                int i = 0;
                DataRow row = result.NewRow();
                foreach (string field in fields)
                {
                    row[i++] = field;
                }
                result.Rows.Add(row);
            }
        }
    }
    return result;
}
 0
Author: PolinaC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 19:07:54

Kolejna do tej listy, Cinchoo ETL - biblioteka open source do odczytu i zapisu wielu formatów plików (CSV, flat file, Xml, JSON itp.)

Przykład poniżej pokazuje jak szybko odczytać plik CSV (nie jest wymagany obiekt POCO)

static void ReadCSV()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoCSVReader(reader))
    {
        writer.WriteLine("id,name");
        writer.WriteLine("1,Carl");
        writer.WriteLine("2,Mark");
        writer.WriteLine("3,Tom");

        writer.Flush();
        stream.Position = 0;

        foreach (dynamic dr in parser)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr.id, dr.name);
        }
    }
}

Przykład poniżej pokazuje, jak odczytać plik CSV za pomocą obiektu poco

public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; } 
}
static void ReadCSV()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoCSVReader<EmployeeRec>(reader))
    {
        writer.WriteLine("id,name");
        writer.WriteLine("1,Carl");
        writer.WriteLine("2,Mark");
        writer.WriteLine("3,Tom");

        writer.Flush();
        stream.Position = 0;

        foreach (var dr in parser)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr.id, dr.name);
        }
    }
}

Zapoznaj się z artykułami na stronie CodeProject na temat tego, jak z niego korzystać.

 0
Author: RajN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-06 16:18:39