Читання файлів 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, але це спрацювало чудово! Дякую!
gillonba

5
+1: Я щойно зламав lumenworks Fast CSV зчитувач у файлі 53Mb. Схоже, кеширування рядків вийшло з ладу після 43000 рядків і проскамував буфер. Спробував 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 дуже просто.
Yongwei Wu

5
Зауважте, що вам може знадобитися додати посилання на Microsoft.VisualBasic, щоб використовувати це. Клацніть правою кнопкою миші ваш проект у Visual Studio, потім виберіть Додати> Довідка та встановіть прапорець Microsoft.VisualBasic.
Дерек Курт

37

Мій досвід полягає в тому, що існує багато різних форматів csv. Особливо, як вони справляються зі скасуванням котирувань та роздільниками в полі.

Ось такі варіанти, в які я зіткнувся:

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

Я спробував багато існуючих CSS-аналізаторів, але немає жодного, який би міг обробити варіанти, в які я зіткнувся. З документації також важко з'ясувати, які варіанти ухилення підтримують парсери.

Зараз у своїх проектах я використовую або VB TextFieldParser, або спеціальний спліттер.


1
Любіть цю відповідь за тестові справи, які ви надали!
Матвій Родатус

2
Основна проблема полягає в тому, що більшість реалізацій не стосується RFC 4180, який описує формат CSV та те, як слід уникати роздільники.
Дженні О'Рейлі

RFC-4180 - це з 2005 року, що здається давнім, але пам’ятайте: .Net Framework був вперше випущений у 2001 році. Також RFC не завжди є офіційними стандартами, і в цьому випадку він не має такої ваги, як, скажімо , ISO-8601 або RFC-761.
Joel

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. Regex для виявлення роздільника від кожного рядка.
  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
Напевно, які проблеми з даними, які самі містять нові рядки?
Doogal

Тепер 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
1818 року

Так, ти можеш. дивіться це посилання stackoverflow.com/questions/20759302 / ...
Райн

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, як з двома масивами. Ви можете знайти мій клас за наступним посиланням: 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-серіалізатор від Майкла Кропата, знайдений тут: Загальний клас для 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.