C #을 사용하여 CSV 파일 읽기


169

간단한 가져 오기 응용 프로그램을 작성 중이며 CSV 파일을 읽고 결과를 DataGrid표시하고 손상된 그리드 파일을 다른 그리드에 표시해야합니다. 예를 들어 다른 그리드에서 5보다 짧은 선을 표시하십시오. 나는 이것을 이렇게하려고 노력하고있다 :

StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
    row = line.Split(',');

    importingData.Add(new Transaction
    {
        Date = DateTime.Parse(row[0]),
        Reference = row[1],
        Description = row[2],
        Amount = decimal.Parse(row[3]),
        Category = (Category)Enum.Parse(typeof(Category), row[4])
    });
}

그러나이 경우에는 어레이에서 작동하기가 매우 어렵습니다. 값을 나누는 더 좋은 방법이 있습니까?


솔루션 주셔서 감사합니다. 답변 게시물로 게시하는 것이 좋습니다. 질문에 포함해도 가독성에 도움이되지 않습니다.
BartoszKP

답변:


363

바퀴를 재발 명하지 마십시오. .NET BCL에 이미있는 기능을 활용하십시오.

  • 에 대한 참조를 추가하십시오 Microsoft.VisualBasic(예, VisualBasic이라고하지만 C #에서도 작동합니다. 결국 모두 IL 일뿐입니다)
  • Microsoft.VisualBasic.FileIO.TextFieldParser클래스를 사용하여 CSV 파일을 구문 분석하십시오.

샘플 코드는 다음과 같습니다.

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData) 
    {
        //Processing row
        string[] fields = parser.ReadFields();
        foreach (string field in fields) 
        {
            //TODO: Process field
        }
    }
}

C # 프로젝트에서 나에게 효과적입니다.

더 많은 링크 / 정보는 다음과 같습니다.


18
VB 라이브러리를 사용하지 않는 방법이 있었으면 좋겠지 만 완벽하게 작동했습니다! 감사합니다!
gillonba

5
+1 : 53Mb 파일에서 lumenworks Fast CSV 리더를 중단했습니다. 43,000 행 이후에 라인 캐싱이 실패하고 버퍼를 스크램블 한 것 같습니다. VB를 시험해 보았습니다 TextFieldParser. 고마워
사라 코딩

10
+1 좋은 답변입니다. 많은 사람들이이 수업이 존재한다는 것을 알지 못합니다. 미래의 시청자가 주목할 점은 메서드가 속성을 설정하기 때문에을 parser.TextFieldType = FieldType.Delimited;호출하면 설정 이 필요하지 않다는 것입니다. parser.SetDelimiters(",");TextFieldType
Brian

10
dotnetperls.com/textfieldparser 도 확인하십시오 . TextFieldParser는 String.Split 및 StreamReader보다 성능이 떨어집니다. 그러나 string.Split과 TextFieldParser에는 큰 차이가 있습니다. TextFieldParser는 열에 쉼표가있는 것과 같은 이상한 경우를 처리합니다. 열의 이름 "text with quote"", and comma"을으로 지정할 수 있으며 text with quote", and comma, 잘못 분리 된 값 대신 올바른 값을 얻을 수 있습니다 . 따라서 CSV가 매우 간단한 경우 String.Split을 선택할 수 있습니다.
Yongwei Wu

5
이를 사용하려면 Microsoft.VisualBasic에 대한 참조를 추가해야 할 수도 있습니다. Visual Studio에서 프로젝트를 마우스 오른쪽 단추로 클릭 한 다음 추가> 참조를 선택하고 Microsoft.VisualBasic 확인란을 선택하십시오.
Derek Kurth

37

내 경험은 많은 다른 CSV 형식이 있다는 것입니다. 특히 필드 내에서 따옴표와 구분 기호 이스케이프 처리 방법.

이것들은 내가 만난 변종입니다.

  • 따옴표는 따옴표로 묶어지고 두 배가됩니다 (예 : 15 "-> field1,"15 "" ", field3
  • 다른 이유로 필드를 인용하지 않으면 따옴표는 변경되지 않습니다. 즉 15 "-> field1,15", fields3
  • 따옴표는 \로 이스케이프됩니다. 즉 15 "-> field1,"15 \ "", field3
  • 따옴표는 전혀 변경되지 않습니다 (항상 올바르게 구문 분석 할 수있는 것은 아닙니다)
  • 구분자는 따옴표로 묶습니다 (excel). 즉 a, b-> field1, "a, b", field3
  • 구분 기호는 \로 이스케이프됩니다. 즉 a, b-> field1, a \, b, field3

기존 csv 파서 중 많은 것을 시도했지만 내가 실행 한 변형을 처리 할 수있는 단일 파서는 없습니다. 파서가 지원하는 이스케이프 변형을 문서에서 찾아내는 것도 어렵습니다.

내 프로젝트에서 VB TextFieldParser 또는 사용자 지정 스플리터를 사용합니다.


1
제공 한 테스트 사례에 대해이 답변을 좋아하십시오!
Matthew Rodatus

2
주요 문제는 대부분의 구현에서 CSV 형식과 구분 기호를 이스케이프 처리하는 방법을 설명하는 RFC 4180에 관심이 없다는 것입니다.
Jenny O'Reilly

RFC-4180은 2005 년부터 시작되었습니다. .Net 프레임 워크는 2001 년에 처음 등장했습니다. 또한 RFC는 항상 공식적인 표준은 아니며,이 경우에는 , ISO-8601 또는 RFC-761.
Joel Coehoorn

23

Nuget의 CsvHelper를 권장 합니다.

(Microsoft.VisualBasic에 대한 참조를 추가하는 것은 옳지 않다고 생각합니다. 추악한 것뿐만 아니라 아마도 크로스 플랫폼이 아닙니다.)


2
C #과 마찬가지로 정확하게 크로스 플랫폼입니다.
PRMan

잘못, Linux의 Microsoft.VisualBasic.dll은 모노 소스에서 가져옵니다. 모노 소스는 Microsoft와 다른 구현이며 구현되지 않은 것이 있습니다. 예를 들면 다음과 같습니다. stackoverflow.com/questions/6644165/…
knocte

(또한, VB 언어는 Mono 프로젝트를 작성 / 개발하는 회사에 집중 한 적이 없으므로 C # 생태계 / 툴링과 비교할 때 노력 측면에서는 뒤쳐져 있습니다.
knocte

1
둘 다 가지고 놀았으므로 CsvHelper클래스 매퍼에 기본 제공 행이 추가 됩니다. 열 헤더 (있는 경우)의 변형을 허용하고 심지어 열 순서의 변형도 허용합니다 (내가 직접 테스트하지는 않았지만). 대체로 그것은 "보다 높은 수준"이라고 느낀다 TextFieldParser.
David

1
.NET Core 2.1
N4ppeL

13

때로는 바퀴를 재발 명하고 싶지 않을 때 라이브러리를 사용하는 것이 좋지만,이 경우 라이브러리를 사용하는 것보다 적은 수의 코드 줄로 동일한 작업을 수행 할 수 있습니다. 사용하기 매우 쉬운 다른 방법이 있습니다.

  1. 이 예제에서는 StreamReader를 사용하여 파일을 읽습니다.
  2. 각 줄에서 구분자를 감지하는 정규식.
  3. 인덱스 0에서 n까지 열을 수집하는 배열

using (StreamReader reader = new StreamReader(fileName))
    {
        string line; 

        while ((line = reader.ReadLine()) != null)
        {
            //Define pattern
            Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

            //Separating columns to array
            string[] X = CSVParser.Split(line);

            /* Do something with X */
        }
    }

4
분명히 그 자체에는 새로운 줄이 포함 된 데이터에 문제가 있습니까?
Doogal

이제 CSV 데이터 파일은 데이터 사이에 빈 줄을 포함하는 것으로 알려져 있지 않지만 그렇게하는 소스가있는 경우 독자를 실행하기 전에 공백이나 아무것도 포함하지 않는 줄을 제거하기 위해 간단한 정규식 테스트를 수행하면됩니다. 다른 예를 보려면 여기를 확인하십시오 : stackoverflow.com/questions/7647716/…
Mana

1
분명히 char-based 접근법은 정규 표현식보다 이러한 종류의 문제에 더 자연 스럽습니다. 인용 부호의 존재에 따라 동작이 달라야합니다.
Casey

6

CSV는 복잡 얻을 수있는 진짜 빨리.

강력하고 잘 테스트 된 것을 사용하십시오.
FileHelpers : www.filehelpers.net

FileHelper는 파일, 문자열 또는 스트림의 고정 길이 또는 구분 된 레코드에서 데이터를 가져 오거나 내보내는 무료 .NET 라이브러리입니다.


5
FileHelper가 한 번에 많은 것을하려고한다고 생각합니다. 파일 구문 분석은 행을 필드로 먼저 분할 한 다음 필드를 데이터로 구문 분석하는 2 단계 프로세스입니다. 기능을 결합하면 마스터-디테일 및 라인 필터링과 같은 것을 처리하기가 어렵습니다.
adrianm


4

이 목록의 또 다른 하나 인 Cinchoo ETL -CSV 파일을 읽고 쓰는 오픈 소스 라이브러리

아래 샘플 CSV 파일

Id, Name
1, Tom
2, Mark

아래와 같이 라이브러리를 사용하여 빠르게로드 할 수 있습니다

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

CSV 파일과 일치하는 POCO 클래스가있는 경우

public class Employee
{
   public int Id { get; set; }
   public string Name { get; set; }
}

아래와 같이 CSV 파일을로드 할 수 있습니다

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

CodeProject 에서 사용 방법에 대한 기사를 확인 하십시오.

면책 조항 : 나는이 도서관의 저자입니다


안녕하세요, CSV 테이블에 CSV를로드 할 수 있습니까-CSV 테이블의 헤더를 미리 알 수 없습니다. CSV의 내용을 Sql 테이블로 미러링
aggie

그래 넌 할수있어. 이 링크를 참조하십시오 stackoverflow.com/questions/20759302/…
RajN

2
private static DataTable ConvertCSVtoDataTable(string strFilePath)
        {
            DataTable dt = new DataTable();
            using (StreamReader sr = new StreamReader(strFilePath))
            {
                string[] headers = sr.ReadLine().Split(',');
                foreach (string header in headers)
                {
                    dt.Columns.Add(header);
                }
                while (!sr.EndOfStream)
                {
                    string[] rows = sr.ReadLine().Split(',');
                    DataRow dr = dt.NewRow();
                    for (int i = 0; i < headers.Length; i++)
                    {
                        dr[i] = rows[i];
                    }
                    dt.Rows.Add(dr);
                }

            }

            return dt;
        }

        private static void WriteToDb(DataTable dt)
        {
            string connectionString =
                "Data Source=localhost;" +
                "Initial Catalog=Northwind;" +
                "Integrated Security=SSPI;";

            using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
                        cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
                        cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";

                        con.Open();
                        cmd.ExecuteNonQuery();
                    }
                }

         }

이 솔루션을 어디에서 복사 했습니까?
MindRoasterMir

0

우선 CSV 란 무엇이며 어떻게 작성해야하는지 이해해야합니다.

  1. 모든 다음 문자열 ( /r/n)은 다음 "테이블"행입니다.
  2. "표"셀은 일부 구분 기호로 구분됩니다. 가장 많이 사용되는 기호는 \t또는,
  3. 모든 셀은이 구분 기호를 포함 할 수 있습니다 (셀은 따옴표 기호로 시작하고이 경우이 기호로 끝나야합니다)
  4. 모든 셀은 /r/n시볼 을 포함 할 수 있습니다 (셀은 따옴표로 시작하고이 경우이 기호로 끝나야합니다)

C # / Visual Basic에서 CSV 파일을 사용하는 가장 쉬운 방법은 표준 Microsoft.VisualBasic라이브러리 를 사용하는 것 입니다. 필요한 참조와 클래스에 다음 문자열을 추가하기 만하면됩니다.

using Microsoft.VisualBasic.FileIO;

예, C #에서 사용할 수 있습니다. 걱정하지 마십시오. 이 라이브러리는 비교적 큰 파일을 읽고 필요한 모든 규칙을 지원하므로 모든 CSV 파일로 작업 할 수 있습니다.

얼마 전이 라이브러리를 기반으로 CSV 읽기 / 쓰기를위한 간단한 클래스를 작성했습니다. 이 간단한 클래스를 사용하면 2 차원 배열과 같은 CSV로 작업 할 수 있습니다. 다음 링크를 통해 내 수업을 찾을 수 있습니다 : https://github.com/ukushu/DataExporter

사용하는 간단한 예 :

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

0

이전 답변을 완료하려면 TextFieldParser또는 string.Split메소드로 파싱 ​​한 CSV 파일의 객체 컬렉션이 필요할 수 있으며 각 줄은 리플렉션을 통해 객체로 변환됩니다. 먼저 CSV 파일의 줄과 일치하는 클래스를 정의해야합니다.

나는 Michael Kropat의 간단한 CSV Serializer를 여기에서 찾았습니다 : Generic 클래스에서 CSV (모든 속성) 까지 그의 메소드를 재사용하여 원하는 클래스의 필드와 속성을 얻었습니다.

다음 방법으로 CSV 파일을 직렬화 해제합니다.

public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
    if (!File.Exists(fileFullPath))
    {
        return null;
    }

    var list = new List<T>();
    var csvFields = GetAllFieldOfClass<T>();
    var fieldDict = new Dictionary<int, MemberInfo>();

    using (TextFieldParser parser = new TextFieldParser(fileFullPath))
    {
        parser.SetDelimiters(delimiter);

        bool headerParsed = false;

        while (!parser.EndOfData)
        {
            //Processing row
            string[] rowFields = parser.ReadFields();
            if (!headerParsed)
            {
                for (int i = 0; i < rowFields.Length; i++)
                {
                    // First row shall be the header!
                    var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
                    if (csvField != null)
                    {
                        fieldDict.Add(i, csvField);
                    }
                }
                headerParsed = true;
            }
            else
            {
                T newObj = new T();
                for (int i = 0; i < rowFields.Length; i++)
                {
                    var csvFied = fieldDict[i];
                    var record = rowFields[i];

                    if (csvFied is FieldInfo)
                    {
                        ((FieldInfo)csvFied).SetValue(newObj, record);
                    }
                    else if (csvFied is PropertyInfo)
                    {
                        var pi = (PropertyInfo)csvFied;
                        pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
                    }
                    else
                    {
                        throw new Exception("Unhandled case.");
                    }
                }
                if (newObj != null)
                {
                    list.Add(newObj);
                }
            }
        }
    }
    return list;
}

public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
    return
        from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
        let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
        orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
        select mi;            
}

0

CsvHelper를 사용하는 것이 좋습니다.

다음은 간단한 예입니다.

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

전체 설명서는 https://joshclose.github.io/CsvHelper 에서 찾을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.