Импорт файлов CSV в .Net

92

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

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

  • 0
    Это дубликат stackoverflow.com/questions/1103495/…
  • 7
    Учитывая, что это было создано годом ранее, чем 1103495, я думаю, что этот вопрос является дубликатом этого.
Показать ещё 3 комментария
Теги:
csv
file
import

12 ответов

46
Лучший ответ

Зайдите в Библиотека с открытым исходным кодом FileHelpers.

  • 1
    К сожалению, это LGPL, который далеко не идеален в корпоративной среде ...
  • 5
    @ Джон, почему ты так говоришь? LGPL не требует, чтобы вы выпускали какой-либо код, если вы не изменили саму библиотеку. (В этом случае имеет смысл в любом случае представить патч.)
Показать ещё 9 комментариев
75

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

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

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

  • 2
    На самом деле в этом классе нет ничего специфичного для VB, кроме его, к сожалению, именованного пространства имен. Я бы определенно выбрал эту библиотеку, если бы мне нужен был только «простой» CSV-парсер, потому что в общем нет ничего, что можно было бы загружать, распространять или беспокоиться. С этой целью я отредактировал VB-ориентированную формулировку этого ответа.
  • 0
    @ Aaronaught Я думаю, что ваши правки в основном улучшаются. Хотя этот RFC не обязательно является авторитетным, так как многие авторы CSV не соблюдают его, например, Excel не всегда использует запятую в файлах "CSV". Также мой предыдущий ответ уже говорил, что класс можно использовать из C #?
Показать ещё 1 комментарий
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();
  • 0
    Это требует доступа к файловой системе. Насколько я знаю, нет способа заставить OLEDB работать с потоками в памяти :(
  • 3
    @UserControl, конечно, требуется доступ к файловой системе. Он спросил об импорте файла CSV
Показать ещё 1 комментарий
12

Если вы ожидаете довольно сложных сценариев для синтаксического анализа CSV, даже не придумывайте, чтобы опрокинуть наш собственный парсер. Есть много отличных инструментов, например FileHelpers, или даже те из CodeProject.

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

  • 0
    Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными в случае изменения связанной страницы. - Из обзора
  • 0
    Спасибо @techspider. Надеюсь, вы заметили, что этот пост относится к бета-версии StackOverflow: D Как уже говорилось, в настоящее время инструменты CSV лучше получены из пакетов Nuget, поэтому я не уверен, что даже ссылки на ссылки не защищены от 8-летней давности. Старые эволюционные технологические циклы
9

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

1, "Bill", "Smith", "Supervisor", "No Comment"

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

К сожалению, меня не цитируют, и я нахожусь на новой строке!

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 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 имеет в себе строку с кавычками. См. этот пост для немного лучшего разложения, а также некоторые ссылки на некоторые соответствующие библиотеки.

6

Мне было скучно, поэтому я модифицировал некоторые вещи, которые я написал. Он пытается инкапсулировать синтаксический анализ в режиме OO, уменьшая количество итераций через файл, он только итерации один раз в верхней строке 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]),
                };
            }
        }
    }
}
5

В CodeProject есть две статьи, которые предоставляют код для решения, в котором используется StreamReader и та, которая импортирует данные CSV, используя Microsoft Text Driver.

2

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

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

Почему бы не попробовать использовать Python вместо С# или VB? Он имеет хороший CSV-модуль для импорта, который делает весь тяжелый подъем для вас.

  • 1
    Не переходите на python из VB ради парсера CSV. Есть один в VB. Хотя это странно, но, похоже, это было проигнорировано в ответах на этот вопрос. msdn.microsoft.com/en-us/library/...
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;
    }
1

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

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

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

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

0

Если вы можете гарантировать, что в данных нет запятых, самым простым способом, вероятно, будет использование String.split.

Например:

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

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

  • 0
    это не оптимальное решение
  • 0
    очень плохо на использование памяти и много накладных расходов. Маленькое должно быть меньше, спасибо несколько килобайт. Определенно не хорошо для 10 МБ CSV!
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню