.Net에서 강력한 형식의 데이터 구조로 CSV 파일 가져 오기 [닫기]


106

강력한 형식의 데이터 구조로 CSV 파일을 가져 오는 가장 좋은 방법은 무엇입니까?




7
이 질문이 1103495보다 1 년 일찍 만들어 졌다는 점을 감안하면이 질문은이 질문과 중복 된 것 같습니다.
— MattH

2
고마워, 맷. 나는 그것들을 함께 연결하려고 한 것이지 어느 것이 먼저 왔는지 나타내지 않았습니다. 이 질문을 가리키는 다른 질문에도 똑같은 텍스트가 있음을 알 수 있습니다. 두 질문을 함께 연결하는 더 좋은 방법이 있습니까?
— Mark Meuer

답변:


74

Microsoft의 TextFieldParser 는 안정적이며 CSV 파일의 경우 RFC 4180 을 따릅니다 . Microsoft.VisualBasic이름 공간 때문에 미루지 마십시오 . .NET Framework의 표준 구성 요소이므로 전역 Microsoft.VisualBasic어셈블리에 대한 참조를 추가하기 만하면 됩니다.

Windows (Mono와 반대) 용으로 컴파일하고 "파손 된"(RFC 비준수) CSV 파일을 구문 분석 할 필요가없는 경우, 이것이 무료이고 제한되지 않고 안정적이기 때문에 이것이 확실한 선택이 될 것입니다. 그리고 적극적으로 지원되며 대부분은 FileHelpers에 대해 말할 수 없습니다.

참고 항목 : 방법 : VB 코드 예제 는 Visual Basic의 쉼표로 구분 된 텍스트 파일에서 읽기


2
불행히도 이름이 지정된 네임 스페이스 외에이 클래스에 대한 VB 관련 사항은 실제로 없습니다. 일반적으로 다운로드, 배포 또는 걱정할 것이 없기 때문에 "간단한"CSV 파서 만 필요한 경우이 라이브러리를 선택합니다. 이를 위해 나는이 답변에서 VB 중심의 문구를 편집했습니다.
— Aaronaught 2011

@Aaronaught 나는 당신의 편집이 대부분 개선이라고 생각합니다. RFC가 반드시 신뢰할 수있는 것은 아니지만 많은 CSV 작성자가이를 준수 하지 않습니다. 예를 들어 Excel 은 "CSV"파일에서 항상 쉼표 를 사용하지 않습니다 . 또한 이전 답변에서 이미 클래스가 C #에서 사용될 수 있다고 말하지 않았습니까?
— MarkJ

TextFieldParser너무 탭으로 구분 된 다른 이상한 엑셀에서 생성 된 cruft에 대한 의지 작동합니다. 귀하의 이전 답변이 라이브러리가 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 솔루션을 선호하지만 ASP.NET 응용 프로그램에서 CSV를 구문 분석해야 할 때 너무 많이 실망했기 때문에 메모하고 싶었습니다.
— UserControl

12

CSV 파싱에 대해 상당히 복잡한 시나리오를 예상하는 경우 자체 파서 롤링을 생각하지 마십시오 . FileHelpers 또는 CodeProject의 도구와 같은 훌륭한 도구가 많이 있습니다 .

요점은 이것은 매우 일반적인 문제이며 많은 소프트웨어 개발자가 이미이 문제에 대해 생각하고 해결했다고 확신 할 수 있습니다.


이 링크가 질문에 답할 수 있지만 여기에 답변의 필수 부분을 포함하고 참조 용 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 무효화 될 수 있습니다. - 검토에서
— techspider

감사합니다. 기술의 올드 진화 사이클
— 존 Limjap

9

Brian은 강력한 형식의 컬렉션으로 변환 할 수있는 좋은 솔루션을 제공합니다.

제공된 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, "Bill", "Smith", "Supervisor", "No Comment"

2, 'Drake,', 'O'Malley', "Janitor,

죄송합니다. 저는 인용되지 않았고 새 줄에 있습니다!


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


2

이를 수행하는 좋은 간단한 방법은 파일을 열고 각 행을 배열, 연결 목록, 선택한 데이터 구조로 읽는 것입니다. 그래도 첫 번째 줄을 처리하는 데주의하십시오.

이것은 머리 위에있을 수 있지만 연결 문자열을 사용하여 직접 액세스하는 방법도있는 것 같습니다 .

C # 또는 VB 대신 Python을 사용해 보지 않겠습니까? 그것은 당신을 위해 모든 무거운 짐을 가져 오는 멋진 CSV 모듈을 가지고 있습니다.


1
CSV 파서를 위해 VB에서 파이썬으로 점프하지 마십시오. VB에 하나가 있습니다. 이상하게도이 질문에 대한 답변에서 무시 된 것 같습니다. msdn.microsoft.com/en-us/library/…
— MarkJ

1

이번 여름에 프로젝트를 위해 .NET에서 CSV 파서를 사용해야했고 Microsoft Jet Text Driver를 사용했습니다. 연결 문자열을 사용하여 폴더를 지정한 다음 SQL Select 문을 사용하여 파일을 쿼리합니다. schema.ini 파일을 사용하여 강력한 유형을 지정할 수 있습니다. 처음에는이 작업을 수행하지 않았지만 IP 번호 나 "XYQ 3.9 SP1"과 같은 항목과 같이 데이터 유형이 즉시 명확하지 않은 잘못된 결과를 얻었습니다.

한 가지 제한 사항은 64 자 이상의 열 이름을 처리 할 수 ​​없다는 것입니다. 잘립니다. 이것은 매우 잘못 설계된 입력 데이터를 다루고 있다는 점을 제외하고는 문제가되지 않아야합니다. ADO.NET DataSet을 반환합니다.

이것이 내가 찾은 최고의 솔루션이었습니다. 최종 사례 중 일부를 놓칠 수 있고 .NET 용 다른 무료 CSV 구문 분석 패키지를 찾지 못했기 때문에 내 자신의 CSV 파서 롤링을 조심할 것입니다.

편집 : 또한 디렉터리 당 하나의 schema.ini 파일 만있을 수 있으므로 필요한 열을 강력하게 입력하기 위해 동적으로 추가했습니다. 지정된 열만 강력하게 입력하고 지정되지 않은 필드를 추론합니다. 유동적 인 70 개 이상의 열 CSV 가져 오기를 처리하고 각 열을 지정하지 않고 오작동하는 열만 지정하고 싶었 기 때문에 정말 감사했습니다.


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

도움을주기 위해 사용할 수있는 라이브러리가있을 수 있지만 아마도 얻을 수있는만큼 간단 할 것입니다. 데이터에 쉼표를 사용할 수 없는지 확인하십시오. 그렇지 않으면 더 잘 구문 분석해야합니다.


이것은 최적의 해결책이 아닙니다
— roundcrisis

메모리 사용량과 많은 오버 헤드가 매우 나쁩니다. 작은 것은 몇 킬로바이트보다 적어야합니다. 10MB csv에는 확실히 좋지 않습니다!
— Piotr Kula

메모리와 파일의 크기에 따라 다릅니다.
— tonymiao
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.