นำเข้าไฟล์ CSV ไปยังโครงสร้างข้อมูลที่พิมพ์อย่างชัดเจนใน. Net [ปิด]


106

วิธีใดเป็นวิธีที่ดีที่สุดในการนำเข้าไฟล์ CSV ไปยังโครงสร้างข้อมูลที่พิมพ์มากเกินไป


ตรวจสอบFileHelpers เปิดห้องสมุดแหล่งที่มา
NotMyself

นี่คือรายการที่ซ้ำกันของstackoverflow.com/questions/1103495/…
Mark Meuer

7
เมื่อพิจารณาว่าสิ่งนี้ถูกสร้างขึ้นเมื่อหนึ่งปีก่อนหน้า 1103495 ฉันคิดว่าคำถามนั้นซ้ำกับคำถามนี้
MattH

2
ขอบคุณ Matt. ฉันแค่พยายามเชื่อมโยงเข้าด้วยกันไม่ได้ระบุว่าอันไหนมาก่อน คุณจะเห็นว่าฉันมีข้อความเดียวกันกับคำถามอื่นที่ชี้ไปที่คำถามนี้มีวิธีที่ดีกว่าในการผูกคำถามสองข้อเข้าด้วยกันหรือไม่?
Mark Meuer

คำตอบ:


74

TextFieldParserของ Microsoft มีความเสถียรและเป็นไปตามRFC 4180สำหรับไฟล์ CSV อย่าถูกMicrosoft.VisualBasicเนมสเปซเลื่อนออกไป เป็นส่วนประกอบมาตรฐานใน. NET Framework เพียงเพิ่มการอ้างอิงไปยังMicrosoft.VisualBasicแอสเซมบลีส่วนกลาง

หากคุณกำลังคอมไพล์สำหรับ Windows (ซึ่งตรงข้ามกับ Mono) และไม่คิดว่าจะต้องแยกวิเคราะห์ไฟล์ CSV ที่ "เสีย" (ไม่สอดคล้องกับ RFC) นี่จะเป็นตัวเลือกที่ชัดเจนเนื่องจากเป็นไฟล์ฟรีไม่ จำกัด และเสถียร และได้รับการสนับสนุนอย่างแข็งขันซึ่งส่วนใหญ่ไม่สามารถพูดได้สำหรับ FileHelpers

ดูเพิ่มเติม: วิธีการ: อ่านจากไฟล์ข้อความที่คั่นด้วยจุลภาคใน Visual Basicสำหรับตัวอย่างโค้ด VB


2
ไม่มีอะไรที่เฉพาะเจาะจงเกี่ยวกับ VB เกี่ยวกับคลาสนี้นอกจากเนมสเปซที่มีชื่อน่าเสียดาย ฉันจะเลือกไลบรารีนี้อย่างแน่นอนถ้าฉันต้องการแค่ตัวแยกวิเคราะห์ CSV แบบ "ธรรมดา" เพราะไม่มีอะไรให้ดาวน์โหลดแจกจ่ายหรือกังวลโดยทั่วไป ด้วยเหตุนี้ฉันจึงแก้ไขวลีที่เน้น VB จากคำตอบนี้
Aaronaught

@Aaronaught ฉันคิดว่าการแก้ไขของคุณส่วนใหญ่เป็นการปรับปรุง แม้ว่า RFC นั้นไม่จำเป็นต้องเชื่อถือได้ แต่เนื่องจากผู้เขียน CSV หลายคนไม่ปฏิบัติตามเช่น Excel ไม่ได้ใช้ลูกน้ำในไฟล์ "CSV" เสมอไป คำตอบก่อนหน้าของฉันยังไม่ได้บอกว่าชั้นเรียนสามารถใช้จาก C # ได้หรือไม่?
MarkJ

TextFieldParserจะทำงานให้คั่นด้วยแท็บและอื่น ๆ ที่แปลก cruft 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
Kevin

1
ฉันไม่ได้บ่น ในความเป็นจริงฉันชอบโซลูชัน OLEDB มากกว่าที่เหลือ แต่ฉันรู้สึกผิดหวังหลายครั้งเมื่อจำเป็นต้องแยกวิเคราะห์ CSV ในแอปพลิเคชัน ASP.NET ดังนั้นจึงต้องการบันทึกไว้
UserControl

12

หากคุณกำลังคาดหวังว่าสถานการณ์ที่ซับซ้อนอย่างเป็นธรรมสำหรับการแยก CSV, ไม่ได้คิดขึ้นจากการกลิ้งตัวแยกวิเคราะห์ของเราเอง มีจำนวนมากของเครื่องมือที่ยอดเยี่ยมออกมีเช่นFileHelpersหรือคนแม้จะมาจากCodeProject

ประเด็นนี้เป็นปัญหาที่พบได้บ่อยและคุณสามารถเดิมพันได้ว่านักพัฒนาซอฟต์แวร์จำนวนมากได้คิดและแก้ไขปัญหานี้แล้ว


แม้ว่าลิงก์นี้อาจตอบคำถามได้ แต่ควรรวมส่วนสำคัญของคำตอบไว้ที่นี่และระบุลิงก์เพื่อการอ้างอิง คำตอบแบบลิงก์เท่านั้นอาจไม่ถูกต้องหากหน้าที่เชื่อมโยงเปลี่ยนไป - จากรีวิว
techspider

ขอบคุณ @techspider ฉันหวังว่าคุณจะทราบว่าโพสต์นี้มาจากช่วงเบต้าของ StackOverflow: D ที่กล่าวกันว่าในปัจจุบันเครื่องมือ CSV มีที่มาที่ดีกว่าจากแพ็คเกจ Nuget ดังนั้นฉันไม่แน่ใจว่าแม้แต่คำตอบของลิงก์จะได้รับการยกเว้นจาก 8 ปีหรือไม่ - วงจรวิวัฒนาการของเทคโนโลยีเก่า
Jon 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', "ภารโรง,

อ๊ะฉันไม่ได้ยกมา แต่ฉันขึ้นบรรทัดใหม่!


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

วิธีง่ายๆที่ดีในการทำคือเปิดไฟล์และอ่านแต่ละบรรทัดลงในอาร์เรย์รายการที่เชื่อมโยงโครงสร้างข้อมูลที่คุณเลือก ระมัดระวังเกี่ยวกับการจัดการบรรทัดแรกแม้ว่า

นี้อาจจะเหนือหัวของคุณ แต่ดูเหมือนว่าจะมีทางตรงในการเข้าถึงพวกเขาเช่นกันโดยใช้สายเชื่อมต่อ

ทำไมไม่ลองใช้ Python แทน C # หรือ VB ล่ะ? มีโมดูล CSV ที่ดีในการนำเข้าซึ่งจะช่วยยกของหนักทั้งหมดให้กับคุณ


1
อย่าข้ามไปที่ python จาก VB เพื่อประโยชน์ในการแยกวิเคราะห์ CSV มีหนึ่งใน VB แม้ว่าจะดูเหมือนว่าจะไม่สนใจคำตอบของคำถามนี้ก็ตาม msdn.microsoft.com/en-us/library/…
MarkJ

1

ฉันต้องใช้ตัวแยกวิเคราะห์ CSV ใน. NET สำหรับโปรเจ็กต์ในช่วงฤดูร้อนนี้และตัดสินใน Microsoft Jet Text Driver คุณระบุโฟลเดอร์โดยใช้สตริงการเชื่อมต่อจากนั้นค้นหาไฟล์โดยใช้คำสั่ง SQL Select คุณสามารถระบุประเภทที่รัดกุมโดยใช้ไฟล์ schema.ini ฉันไม่ได้ทำในตอนแรก แต่แล้วฉันก็ได้รับผลลัพธ์ที่ไม่ดีโดยที่ประเภทของข้อมูลไม่ปรากฏในทันทีเช่นหมายเลข IP หรือรายการเช่น "XYQ 3.9 SP1"

ข้อ จำกัด อย่างหนึ่งที่ฉันพบคือไม่สามารถจัดการชื่อคอลัมน์ที่สูงกว่า 64 อักขระได้ มันตัดทอน นี่ไม่น่าจะเป็นปัญหายกเว้นว่าฉันกำลังจัดการกับข้อมูลอินพุตที่ออกแบบมาไม่ดี ส่งคืนชุดข้อมูล ADO.NET

นี่เป็นทางออกที่ดีที่สุดที่ฉันพบ ฉันจะระมัดระวังในการเปลี่ยนโปรแกรมแยกวิเคราะห์ CSV ของตัวเองเนื่องจากฉันอาจจะพลาดบางกรณีและฉันไม่พบแพ็คเกจการแยกวิเคราะห์ CSV ฟรีสำหรับ. NET

แก้ไข: นอกจากนี้ยังสามารถมีไฟล์ schema.ini ได้เพียงไฟล์เดียวต่อไดเรกทอรีดังนั้นฉันจึงต่อท้ายแบบไดนามิกเพื่อพิมพ์คอลัมน์ที่จำเป็นอย่างมาก โดยจะพิมพ์เฉพาะคอลัมน์ที่ระบุอย่างชัดเจนและอนุมานสำหรับฟิลด์ที่ไม่ได้ระบุ ฉันชื่นชมสิ่งนี้มากเนื่องจากฉันกำลังจัดการกับการนำเข้า CSV ที่เป็นของเหลว 70+ คอลัมน์และไม่ต้องการระบุแต่ละคอลัมน์มีเพียงคอลัมน์ที่ทำงานไม่ดีเท่านั้น


ทำไมไม่ 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

แย่มากกับการใช้หน่วยความจำและค่าใช้จ่ายมากมาย เล็กควรน้อยขอบคุณไม่กี่กิโลไบต์ ไม่ดีแน่สำหรับ csv 10mb!
Piotr Kula

ขึ้นอยู่กับขนาดของหน่วยความจำและไฟล์ของคุณ
tonymiao
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.