Імпортуйте файл CSV в сильно набрану структуру даних у .Net [закрито]


106

Який найкращий спосіб імпортувати файл CSV у сильно набрану структуру даних?




7
Вважаючи, що це було створено на рік раніше, ніж 1103495, я думаю, що це питання є дублікатом цього.
Матвія

2
Спасибі, Метт. Я просто намагався пов’язати їх разом, а не вказати, хто з них прийшов першим. Ви побачите, що я маю саме такий самий текст на іншому запитанні, що вказує на це. Чи є кращий спосіб зв’язати два питання разом?
Марк Меуер

Відповіді:


74

TextFieldParser від Microsoft стабільний і відповідає RFC 4180 для файлів CSV. Не відкладайте Microsoft.VisualBasicпростору імен; це стандартний компонент в .NET Framework, просто додайте посилання на глобальну Microsoft.VisualBasicзбірку.

Якщо ви компілюєте для Windows (на відміну від Mono) і не передбачаєте, що вам доведеться розбирати "зламані" (не сумісні з RFC) CSV файли, то це був би очевидний вибір, оскільки це вільний, необмежений, стабільний, і активно підтримується, більшість з яких не можна сказати для FileHelpers.

Дивіться також: Як: Прочитайте текстові файли, розміщені комами у Visual Basic, для прикладу коду VB.


2
Насправді немає нічого специфічного для VB щодо цього класу, крім його, на жаль, названого простору імен. Я б точно вибрав цю бібліотеку, якби мені знадобився лише "простий" CSV-аналізатор, тому що загалом нема чого завантажувати, розповсюджувати чи турбуватися. З цією метою я відредагував фразу, орієнтовану на VB.
Aaronaught

@Aaronaught Я думаю, що ваші зміни в основному є вдосконаленням. Хоча цей RFC не обов'язково є авторитетним, оскільки багато авторів CSV цього не дотримуються, наприклад, Excel не завжди використовує кому у файлах "CSV". Також у моїй попередній відповіді вже не було сказано, що клас можна використовувати з C #?
MarkJ

TextFieldParserПрацюватиме з роздільниками табуляції і інші дивні Excel згенерованого мотлоху теж. Я усвідомлюю, що ваша попередня відповідь не стверджувала, що бібліотека є специфічною для VB, вона просто натрапила на мене як на думку про те, що вона справді призначена для VB, а не призначена для використання від C #, що, я не думаю, що це випадок - в MSVB є кілька дійсно корисних занять.
Aaronaught

21

Використовуйте з'єднання 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();

Для цього потрібен доступ до файлової системи. Наскільки я знаю, немає ніякого способу змусити OLEDB працювати з потоками пам'яті :(
UserControl

3
@UserControl, звичайно, потрібен доступ до файлової системи. Він запитав про імпорт файлу CSV
Кевін

1
Я не нарікаю. Насправді я вважаю за краще рішення OLEDB над рештою, але мені так часто було неприємно, коли потрібно було проаналізувати CSV в додатках ASP.NET, тому хотілося це відзначити.
UserControl

12

Якщо ви очікуєте досить складних сценаріїв для розбору CSV, навіть не придумайте прокручувати наш власний парсер . Існує багато чудових інструментів, таких як FileHelpers або навіть таких від CodeProject .

Справа в тому, що це досить поширена проблема, і можна покластися на те, що багато розробників програмного забезпечення вже замислювались і вирішували цю проблему.


Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться. - З огляду
techspider

Дякую @techspider Я сподіваюся, ви помітили, що ця публікація була з бета-періоду StackOverflow: D На сьогоднішній день інструменти CSV краще отримувати з пакунків Nuget - тож я не впевнений, що навіть 8-річні відповіді на посилання не захищені. -продовжений цикл еволюції технології
Джон Лімжап,

9

Брайан дає приємне рішення для перетворення його на сильно набрану колекцію.

Більшість наведених методів розбору CSV не враховують поля, що з’являються, або деякі інші тонкощі файлів CSV (наприклад, поля обрізки). Ось код, яким я особисто користуюся. Він трохи шорсткий по краях і майже не повідомляє про помилки.

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

Зауважте, що це не обробляє крайовий випадок полів, не змінених подвійними лапками, але meerley, що має в ньому рядок, що цитується. Дивіться цю публікацію для кращого розширення, а також посилання на деякі власні бібліотеки.


9

Я згоден з @ NotMyself . FileHelpers добре перевірений і обробляє всілякі крайові справи, з якими вам з часом доведеться мати справу, якщо це зробити самостійно. Погляньте на те, що робить FileHelpers, і напишіть своє, лише якщо ви абсолютно впевнені, що або (1) вам ніколи не знадобиться обробляти крайові випадки, що робить FileHelpers, або (2) ви любите писати подібні речі і збираєтесь будьте раді, коли вам доведеться розбирати такі речі:

1, "Білл", "Сміт", "Супервізор", "Без коментарів"

2, "Дрейк", "О'Меллі", "Двірник,

На жаль, я не цитую, і я переходжу на новий рядок!


6

Мені було нудно, я змінив деякі речі, які я написав. Спробуйте зробити інкапсуляцію синтаксичного аналізу в порядку OO, скорочуючи кількість ітерацій через файл, він лише повторюється один раз у верхньому передбачуванні.

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

Хороший простий спосіб зробити це - відкрити файл і прочитати кожен рядок у масиві, зв'язаному списку, структурі даних за вибором. Але будьте обережні щодо обробки першого рядка.

Це може бути над вашою головою, але, мабуть, існує прямий спосіб отримати доступ до них, також використовуючи рядок з'єднання .

Чому б не спробувати використовувати Python замість C # або VB? У ньому є гарний модуль CSV для імпорту, який робить все важке підйом для вас.


1
Не стрибайте на пітон з VB заради аналізатора CSV. У VB є один. Хоча дивно, мабуть, його ігнорували у відповідях на це питання. msdn.microsoft.com/en-us/library/…
MarkJ

1

Мені довелося використати аналізатор CSV в .NET для проекту цього літа і влаштувався на Microsoft Jet Text Driver. Ви вказуєте папку за допомогою рядка з'єднання, а потім запитуєте файл за допомогою оператора SQL Select. Ви можете вказати сильні типи, використовуючи файл schema.ini. Я цього не робив спочатку, але потім отримав погані результати, коли тип даних не був відразу очевидним, наприклад, IP-адреси або запис типу "XYQ 3.9 SP1".

Одне обмеження, на яке я зіткнувся, полягає в тому, що він не може обробляти назви стовпців, що перевищують 64 символи; він усікається. Це не повинно бути проблемою, хіба що я мав справу з дуже погано розробленими вхідними даними. Він повертає ADO.NET DataSet.

Це було найкраще рішення, яке я знайшов. Я б насторожено став прокручувати свій власний аналізатор CSV, оскільки я, мабуть, пропустив би деякі кінцеві випадки, і я не знайшов жодних інших безкоштовних пакетів CSV для розбору .NET там.

EDIT: Крім того, у каталозі може бути лише один файл schema.ini, тому я динамічно додаю до нього сильно вводити потрібні стовпці. Він буде лише вводити вказані стовпці та робити висновки для будь-якого не визначеного поля. Я дуже оцінив це, оскільки я мав справу з імпортом текучого CSV стовпчика 70+ і не хотів вказувати кожен стовпець, лише невідповідний.


Чому б VB.NET не вбудований у CSV-аналізатор? msdn.microsoft.com/en-us/library/…
MarkJ

1

Я набрав якийсь код. Результат у datagridviewer виглядав непогано. Він розбирає один рядок тексту до масиву об’єктів.

    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

Якщо ви можете гарантувати відсутність коми в даних, то найпростішим способом було б скористатися String.split .

Наприклад:

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

Можуть бути бібліотеки, які ви можете використовувати, щоб допомогти, але це, мабуть, так просто, як ви можете отримати. Просто переконайтеся, що ви не можете мати коми в даних, інакше вам доведеться їх краще розібрати.


це не оптимальне рішення
кругла криза

дуже погано використовувати пам'ять і багато накладних витрат. Малий повинен менше дякувати на кілька кілобайт. Однозначно не годиться для 10-мегабайт csv!
Пьотр Кула

Це залежить від розміру вашої пам'яті та файлу.
tonymiao
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.