Импортировать файл CSV в строго типизированную структуру данных в .Net [закрыто]

106

Как лучше всего импортировать CSV-файл в строго типизированную структуру данных?

Мэтт
источник
Это дубликат stackoverflow.com/questions/1103495/…
Марк Меуэр
7
Учитывая, что он был создан годом ранее, чем 1103495, я думаю, что этот вопрос является дубликатом этого.
MattH
2
Спасибо, Мэтт. Я просто пытался связать их вместе, а не указывать, какой из них пришел первым. Вы увидите, что у меня точно такой же текст по другому вопросу, указывающий на этот. Есть ли лучший способ связать два вопроса вместе?
Марк Меуэр

Ответы:

74

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

Если вы компилируете для Windows (в отличие от Mono) и не ожидаете, что вам придется анализировать «сломанные» (не соответствующие RFC) файлы CSV, то это будет очевидным выбором, поскольку он бесплатный, неограниченный, стабильный, и активно поддерживается, чего нельзя сказать о FileHelpers.

См. Также: Как читать из текстовых файлов с разделителями-запятыми в Visual Basic для примера кода VB.

MarkJ
источник
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. Надеюсь, вы заметили, что этот пост был из периода бета-тестирования 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;
}

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

ICR
источник
9

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

1, «Билл», «Смит», «Супервайзер», «Без комментариев»

2, «Дрейк», «О'Мэлли», «Дворник»,

Ой, меня не цитируют и я на новой строчке!

Джон Гэллоуэй
источник
6

Мне было скучно, поэтому я изменил кое-что, что написал. Он пытается инкапсулировать синтаксический анализ в объектно-ориентированном стиле, сокращая количество итераций через файл, он повторяется только один раз в верхней части foreach.

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
Не переходите на Python с VB ради парсера CSV. Один в VB. Хотя, как ни странно, в ответах на этот вопрос его, кажется, проигнорировали. msdn.microsoft.com/en-us/library/…
MarkJ 02
1

Этим летом мне пришлось использовать парсер CSV в .NET для проекта, и я остановился на драйвере Microsoft Jet Text. Вы указываете папку с помощью строки подключения, а затем запрашиваете файл с помощью оператора SQL Select. Вы можете указать строгие типы с помощью файла schema.ini. Сначала я этого не делал, но потом я получал плохие результаты, когда тип данных не сразу определялся, например номера IP или запись типа «XYQ 3.9 SP1».

Я столкнулся с одним ограничением: он не может обрабатывать имена столбцов длиной более 64 символов; он усекает. Это не должно быть проблемой, за исключением того, что я имел дело с очень плохо спроектированными входными данными. Он возвращает набор данных ADO.NET.

Это было лучшее решение, которое я нашел. Я бы опасался развертывать свой собственный анализатор CSV, поскольку я, вероятно, пропустил бы некоторые из конечных случаев, и я не нашел там других бесплатных пакетов синтаксического анализа CSV для .NET.

РЕДАКТИРОВАТЬ: Кроме того, в каталоге может быть только один файл schema.ini, поэтому я динамически добавлял к нему, чтобы строго ввести необходимые столбцы. Он будет строго печатать только указанные столбцы и делать выводы для любого неуказанного поля. Мне это очень понравилось, так как я имел дело с импортом жидкого CSV с более чем 70 столбцами и не хотел указывать каждый столбец, а только некорректно работающие.

pbh101
источник
Почему не VB.NET, встроенный в парсер CSV? msdn.microsoft.com/en-us/library/…
MarkJ 02
1

Я ввел код. Результат в средстве просмотра данных выглядел хорошо. Он анализирует одну строку текста в массив объектов.

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

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

Майк Стоун
источник
это не оптимальное решение
roundcrisis
очень плохо использует память и накладные расходы. Маленький должен быть меньше нескольких килобайт. Определенно не подходит для CSV 10 МБ!
Петр Кула
Это зависит от размера вашей памяти и файла.
tonymiao