Чтение файлов CSV с использованием C #


169

Я пишу простое приложение для импорта, и мне нужно прочитать файл CSV, показать результат в DataGridи показать поврежденные строки файла CSV в другой сетке. Например, покажите линии, которые короче 5 значений в другой сетке. Я пытаюсь сделать это так:

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

но в этом случае очень сложно работать с массивами. Есть ли лучший способ разделить значения?


Спасибо за ваше решение. Подумайте о том, чтобы опубликовать его как пост с ответом - включение его в вопрос не способствует его удобочитаемости.
BartoszKP

Ответы:


363

Не изобретай велосипед. Воспользуйтесь преимуществами того, что уже есть в .NET BCL.

  • добавить ссылку на Microsoft.VisualBasic(да, он говорит VisualBasic, но он работает в C # так же хорошо - помните, что в конце все это просто IL)
  • использовать Microsoft.VisualBasic.FileIO.TextFieldParserкласс для разбора файла CSV

Вот пример кода:

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

Это прекрасно работает для меня в моих проектах на C #.

Вот еще несколько ссылок / информации:


18
Я действительно хотел бы, чтобы был способ, который не использовал библиотеки VB, но это работало отлично! Спасибо!
Гиллонба

5
+1: я только что разбил читатель lumenworks Fast CSV на 53Mb файле. Похоже, что кэширование строк завершилось неудачно после 43 000 строк и закодировал буфер. Попробовал VB, TextFieldParserи он сделал свое дело. Спасибо
ушел кодирование

10
+1 Отличный ответ, так как многие люди не знают, что этот класс существует. Для будущих зрителей обратите внимание на то, что parser.TextFieldType = FieldType.Delimited;при вызове настройка не требуется parser.SetDelimiters(",");, поскольку метод устанавливает TextFieldTypeсвойство для вас.
Брайан

10
Также проверьте это: dotnetperls.com/textfieldparser . TextFieldParser имеет худшую производительность, чем String.Split и StreamReader. Однако есть большая разница между string.Split и TextFieldParser. TextFieldParser обрабатывает странные случаи, такие как запятая в столбце: вы можете назвать столбец как "text with quote"", and comma", и вы можете получить правильное значение text with quote", and commaвместо неправильно разделенных значений. Таким образом, вы можете выбрать String.Split, если вы CSV очень прост.
Юнвэй Ву

5
Обратите внимание, что вам может понадобиться добавить ссылку на Microsoft.VisualBasic, чтобы использовать это. Щелкните правой кнопкой мыши свой проект в Visual Studio, затем выберите «Добавить»> «Ссылка» и установите флажок «Microsoft.VisualBasic».
Дерек Курт

37

Мой опыт показывает, что существует много разных форматов CSV. Особенно, как они обрабатывают экранирование кавычек и разделителей внутри поля.

Вот варианты, с которыми я столкнулся:

  • кавычки заключаются в двойные кавычки (Excel), т.е. 15 "-> field1," 15 "" ", field3
  • кавычки не изменяются, если поле не указано по какой-либо другой причине. т.е. 15 "-> field1,15", fields3
  • кавычки экранируются с помощью \. т.е. 15 "-> field1," 15 \ "", field3
  • кавычки не меняются вообще (это не всегда возможно правильно разобрать)
  • разделитель цитируется (Excel). то есть a, b -> field1, "a, b", field3
  • разделитель экранирован \. то есть a, b -> field1, a \, b, field3

Я перепробовал многие из существующих парсеров csv, но нет ни одного, который мог бы обработать варианты, с которыми я столкнулся. Также трудно найти из документации, какие экранирующие варианты поддерживаются парсерами.

В моих проектах я сейчас использую либо VB TextFieldParser, либо пользовательский сплиттер.


1
Любите этот ответ для тестовых случаев, которые вы предоставили!
Мэтью Родат

2
Основная проблема заключается в том, что большинство реализаций не заботятся о RFC 4180, который описывает формат CSV и как экранировать разделители.
Дженни О'Рейли

RFC-4180 с 2005 года, который кажется устаревшим, но помните: .Net Framework впервые появился в 2001 году. Кроме того, RFC не всегда являются официальными стандартами, и в этом случае он не имеет такой же вес, как, скажем, , ISO-8601 или RFC-761.
Джоэл Коухорн

23

Я рекомендую CsvHelper от Nuget .

(Добавление ссылки на Microsoft.VisualBasic просто не кажется правильным, это не только некрасиво, но, вероятно, даже не кроссплатформенно.)


2
Это так же кроссплатформенно, как C #.
PRMan

неправильно, Microsoft.VisualBasic.dll в Linux поступает из источников Mono, реализация которых отличается от реализации Microsoft, и есть некоторые вещи, которые не реализованы, например: stackoverflow.com/questions/6644165/…
knocte

(Кроме того, язык VB никогда не фокусировался на компаниях, которые были вовлечены в создание / разработку проекта Mono, поэтому он значительно отстает в плане усилий по сравнению с экосистемой / инструментами C #.)
knocte

1
Поиграв с обоими, я бы добавил, что CsvHelperпоставляется со встроенным рядом в класс маппер; он допускает изменения в заголовках столбцов (если они есть) и даже очевидные изменения в порядке столбцов (хотя я сам не проверял последний). В целом он чувствует себя гораздо более "высокого уровня", чем TextFieldParser.
Дэвид

1
да, пространство имен Microsoft.VisualBasic недоступно в .NET Core 2.1
N4ppeL

13

Иногда использование библиотек - это здорово, если вы не хотите изобретать велосипед, но в этом случае можно выполнить ту же работу с меньшим количеством строк кода и легче читать по сравнению с использованием библиотек. Вот другой подход, который я считаю очень простым в использовании.

  1. В этом примере я использую StreamReader для чтения файла
  2. Регулярно определяйте разделитель для каждой строки.
  3. Массив для сбора столбцов с индексом от 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 */
        }
    }

4
Конечно, что есть проблемы с данными, которые сами содержат новые строки?
Доогал

Теперь CSV-файлы данных не знают, что они содержат пустые строки между данными, но если у вас есть источник, который делает это, в этом случае я просто выполнил бы простой тест регулярных выражений, чтобы удалить пробелы или строки, не содержащие ничего, перед запуском программы чтения. проверьте здесь другие примеры: stackoverflow.com/questions/7647716/…
Мана,

1
Конечно, подход на основе символов более естественен для такого рода проблем, чем регулярное выражение. В зависимости от наличия кавычек поведение должно быть разным.
Кейси

6

CSV может усложниться очень быстро.

Используйте что-то надежное и проверенное:
FileHelpers: www.filehelpers.net

FileHelpers - это бесплатная и простая в использовании библиотека .NET для импорта / экспорта данных из файлов фиксированной длины или с разделителями в файлах, строках или потоках.


5
Я думаю, FileHelper пытается сделать многое за один раз. Синтаксический анализ файлов - это двухэтапный процесс, в котором вы сначала разбиваете строки на поля, а затем разбиваете поля на данные. Объединение функций затрудняет обработку таких вещей, как мастер-детализация и фильтрация строк.
Адриан


4

Еще один в этом списке, Cinchoo ETL - библиотека с открытым исходным кодом для чтения и записи файлов CSV

Для примера файла CSV ниже

Id, Name
1, Tom
2, Mark

Быстро вы можете загрузить их, используя библиотеку, как показано ниже

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Если у вас есть класс POCO, соответствующий файлу CSV

public class Employee
{
   public int Id { get; set; }
   public string Name { get; set; }
}

Вы можете использовать его для загрузки файла CSV, как показано ниже

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

Пожалуйста, ознакомьтесь со статьями в CodeProject о том, как его использовать.

Отказ от ответственности: я автор этой библиотеки


Привет, вы можете загрузить CSV в таблицу SQL - я не знаю заголовок в таблице CSV до этого. Просто зеркально отразите то, что в CSV для таблицы Sql
aggie

Да, ты можешь. Пожалуйста, смотрите эту ссылку 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();
                    }
                }

         }

откуда вы скопировали это решение?
MindRoasterMir

0

Прежде всего необходимо понять, что такое CSV и как его написать.

  1. Каждая следующая строка ( /r/n) является следующей строкой таблицы.
  2. Ячейки таблицы разделены каким-либо символом-разделителем. Наиболее часто используемые символы - это \tили,
  3. Возможно, каждая ячейка может содержать этот символ-разделитель (в этом случае ячейка должна начинаться с символа кавычек и заканчиваться этим символом)
  4. Каждая ячейка, возможно, может содержать /r/nсимволы (ячейка должна начинаться с символа кавычек и заканчиваться этим символом в этом случае)

Самый простой способ для C # / Visual Basic работать с файлами CSV - это использовать стандартную Microsoft.VisualBasicбиблиотеку. Вам просто нужно добавить необходимую ссылку и следующую строку в ваш класс:

using Microsoft.VisualBasic.FileIO;

Да, вы можете использовать его в C #, не волнуйтесь. Эта библиотека может читать относительно большие файлы и поддерживает все необходимые правила, поэтому вы сможете работать со всеми файлами CSV.

Некоторое время назад я написал простой класс для чтения / записи CSV на основе этой библиотеки. Используя этот простой класс, вы сможете работать с CSV, как с 2-х мерным массивом. Вы можете найти мой класс по следующей ссылке: https://github.com/ukushu/DataExporter

Простой пример использования:

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

0

Чтобы завершить предыдущие ответы, может понадобиться набор объектов из его CSV-файла, проанализированный методом TextFieldParserили string.Splitметодом, а затем каждая строка преобразуется в объект с помощью Reflection. Очевидно, что сначала вам нужно определить класс, который соответствует строкам файла CSV.

Я использовал простой CSV Serializer от Майкла Кропата, который был найден здесь: общий класс для CSV (все свойства), и повторно использовал его методы для получения полей и свойств желаемого класса.

Я десериализирую свой CSV-файл следующим способом:

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

0

Я настоятельно рекомендую использовать CsvHelper.

Вот быстрый пример:

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

Полная документация может быть найдена в: https://joshclose.github.io/CsvHelper

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.