.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.