อ่านไฟล์ CSV โดยใช้ C #


169

ฉันกำลังเขียนแอปพลิเคชั่นนำเข้าอย่างง่ายและจำเป็นต้องอ่านไฟล์ CSV แสดงผลลัพธ์ในDataGridและแสดงบรรทัดที่เสียหายของไฟล์ CSV ในกริดอื่น ตัวอย่างเช่นแสดงบรรทัดที่สั้นกว่า 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

ดูเพิ่มเติมที่stackoverflow.com/questions/1941392/…
Malcolm

คำตอบ:


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: ฉันเพิ่งแตกเครื่องอ่าน CSV แบบเร็ว lumenworks ลงในไฟล์ 53Mb ดูเหมือนว่าการแคชบรรทัดล้มเหลวหลังจาก 43,000 แถวและกวนบัฟเฟอร์ ลองใช้ VB TextFieldParserและได้ทำเคล็ดลับ ขอบคุณ
Gone Coding

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แทนที่จะเป็นค่าที่แยกจากกันอย่างผิด ๆ ดังนั้นคุณอาจต้องการเลือกใช้ String.Split หากคุณ csv นั้นง่ายมาก
Yongwei Wu

5
โปรดทราบว่าคุณอาจต้องเพิ่มการอ้างอิงไปยัง Microsoft.VisualBasic เพื่อใช้สิ่งนี้ คลิกขวาที่โครงการของคุณใน Visual Studio จากนั้นเลือกเพิ่ม> การอ้างอิงและทำเครื่องหมายในช่องสำหรับ Microsoft.VisualBasic
Derek Kurth

37

ประสบการณ์ของฉันคือมีหลายรูปแบบ CSV วิธีพิเศษที่พวกเขาจัดการกับการหลบหนีของคำพูดและตัวคั่นภายในเขตข้อมูล

นี่คือตัวแปรที่ฉันพบ:

  • คำพูดที่ยกมาและสองเท่า (excel) เช่น 15 "-> field1," 15 "" ", field3
  • เครื่องหมายคำพูดจะไม่เปลี่ยนแปลงเว้นแต่จะมีการอ้างอิงฟิลด์ด้วยเหตุผลอื่น เช่น 15 "-> field1,15", field3
  • คำพูดจะหนีด้วย \ เช่น 15 "-> field1," 15 \ "", field3
  • คำพูดจะไม่เปลี่ยนแปลงเลย (นี่ไม่สามารถแยกวิเคราะห์ได้อย่างถูกต้อง)
  • อ้างถึงตัวคั่น (excel) เช่น a, b -> field1, "a, b", field3
  • ตัวคั่นหนีด้วย \ เช่น a, b -> field1, a \, b, field3

ฉันได้ลองใช้ตัวแยกวิเคราะห์ csv ที่มีอยู่หลายตัว แต่ไม่มีอันเดียวที่สามารถจัดการกับตัวแปรต่างๆที่ฉันพบ นอกจากนี้ยังเป็นเรื่องยากที่จะหาจากเอกสารซึ่งแบ่งสายพันธุ์ที่ parsers สนับสนุน

ในโครงการของฉันตอนนี้ฉันใช้ VB TextFieldParser หรือตัวแยกสัญญาณที่กำหนดเอง


1
รักคำตอบนี้สำหรับกรณีทดสอบที่คุณให้!
Matthew Rodatus

2
ปัญหาหลักคือการใช้งานส่วนใหญ่ไม่สนใจเกี่ยวกับ RFC 4180 ที่อธิบายถึงรูปแบบ CSV และวิธีที่ตัวคั่นควรจะหลบหนี
Jenny O'Reilly

RFC-4180 มาจากปี 2005 ซึ่งดูเก่าตอนนี้ แต่โปรดจำไว้ว่า:. Net framework ออกมาเป็นครั้งแรกในปี 2544 นอกจากนี้ RFCs นั้นไม่ได้เป็นมาตรฐานอย่างเป็นทางการเสมอไปและในกรณีนี้มันไม่ได้มีน้ำหนักเท่ากัน , ISO-8601 หรือ RFC-761
Joel Coehoorn

23

ผมขอแนะนำให้CsvHelper จาก Nuget

(การเพิ่มการอ้างอิงไปยัง Microsoft.VisualBasic เพียงแค่รู้สึกไม่ถูกต้องไม่เพียง แต่น่าเกลียดมันอาจไม่ได้ข้ามแพลตฟอร์ม)


2
มันเหมือนกับ cross-platform อย่างที่ C # เป็น
PRMan

ผิด Microsoft.VisualBasic.dll ใน Linux มาจากแหล่ง Mono ซึ่งมีการใช้งานที่แตกต่างจาก Microsoft และมีบางสิ่งที่ไม่ได้ดำเนินการตัวอย่างเช่น: stackoverflow.com/questions/6644165/…
knocte

(พลัส, VB ภาษาไม่เคยมีโฟกัสใด ๆ ภายใต้ บริษัท ที่ได้มีส่วนร่วมในการสร้าง / พัฒนาโครงการโมโนจึงเป็นวิธีการที่อยู่เบื้องหลังในแง่ของความพยายามเมื่อเทียบกับ C # ระบบนิเวศ / เครื่องมือ.)
knocte

1
ต้องเล่นกับทั้งสองฉันจะเพิ่มที่CsvHelperมาพร้อมกับแถวในตัว mapper ชั้น; มันช่วยให้การเปลี่ยนแปลงในส่วนหัวคอลัมน์ (ถ้ามี) และแม้กระทั่งการเปลี่ยนแปลงในลำดับคอลัมน์ (แม้ว่าฉันไม่ได้ทดสอบตัวเองหลัง) ทั้งหมดในทุกมันให้ความรู้สึกมากขึ้น "ระดับสูง" TextFieldParserกว่า
David

1
yup, Microsoft.VisualBasic namespace ไม่พร้อมใช้งานบน. NET Core 2.1
N4ppeL

13

บางครั้งการใช้ห้องสมุดก็เจ๋งเมื่อคุณไม่ต้องการสร้างล้อใหม่ แต่ในกรณีนี้เราสามารถทำงานเดียวกันโดยมีรหัสบรรทัดน้อยลงและอ่านง่ายกว่าเมื่อใช้ห้องสมุด นี่คือแนวทางที่แตกต่างซึ่งฉันพบว่าใช้งานง่ายมาก

  1. ในตัวอย่างนี้ฉันใช้ StreamReader เพื่ออ่านไฟล์
  2. Regex เพื่อตรวจจับตัวคั่นจากแต่ละบรรทัด
  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 ไม่ทราบว่ามีเส้นว่างระหว่างข้อมูล แต่ถ้าคุณมีแหล่งที่ทำเช่นนั้นในกรณีนี้ฉันจะทำการทดสอบ regex อย่างง่าย ๆ เพื่อลบช่องว่างหรือบรรทัดที่ไม่มีสิ่งใดมาก่อนที่จะรันโปรแกรมอ่าน ตรวจสอบที่นี่สำหรับตัวอย่างที่แตกต่าง: stackoverflow.com/questions/7647716/…
มานะ

1
แน่นอนวิธีการใช้ถ่านเป็นธรรมชาติมากขึ้นสำหรับปัญหาประเภทนี้กว่า regex ขึ้นอยู่กับการมีอยู่ของเครื่องหมายคำพูดพฤติกรรมที่ควรจะแตกต่างกัน
Casey

6

CSV สามารถซับซ้อนได้อย่างรวดเร็วจริง

ใช้สิ่งที่แข็งแกร่งและผ่านการทดสอบอย่างดี:
FileHelpers: www.filehelpers.net

FileHelpers เป็นไลบรารี. 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);
   }
}

หากคุณมีคลาส POCO ที่ตรงกับไฟล์ CSV

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 ไปยังตาราง Sql ได้หรือไม่ฉันไม่ทราบส่วนหัวในตาราง 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/nsybols (เซลล์ต้องเริ่มต้นด้วยเครื่องหมายคำพูดและลงท้ายด้วยสัญลักษณ์นี้ในกรณีนี้)

วิธีที่ง่ายที่สุดสำหรับ C # / Visual Basic ในการทำงานกับไฟล์ CSV คือการใช้Microsoft.VisualBasicไลบรารีมาตรฐาน คุณเพียงแค่ต้องเพิ่มการอ้างอิงที่จำเป็นและสตริงต่อไปนี้ในชั้นเรียนของคุณ:

using Microsoft.VisualBasic.FileIO;

ใช่คุณสามารถใช้มันใน C # ไม่ต้องกังวล ไลบรารีนี้สามารถอ่านไฟล์ที่ค่อนข้างใหญ่และรองรับกฎที่จำเป็นทั้งหมดดังนั้นคุณจะสามารถทำงานกับไฟล์ CSV ทั้งหมดได้

เมื่อไม่นานมานี้ฉันได้เขียนคลาสง่าย ๆ สำหรับการอ่าน / เขียน CSV ตามไลบรารีนี้ เมื่อใช้คลาสแบบง่ายนี้คุณจะสามารถทำงานกับ CSV เช่นเดียวกับอาร์เรย์ 2 มิติ คุณสามารถหาชั้นเรียนของฉันได้จากลิงค์ต่อไปนี้: 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

เพื่อให้คำตอบก่อนหน้านี้เสร็จสมบูรณ์หนึ่งอาจต้องรวบรวมวัตถุจากไฟล์ CSV ของเขาแยกวิเคราะห์โดยTextFieldParserหรือstring.Splitวิธีการแล้วแต่ละบรรทัดแปลงเป็นวัตถุผ่านสะท้อน ก่อนอื่นคุณต้องกำหนดคลาสที่ตรงกับบรรทัดของไฟล์ CSV

ฉันใช้ CSV Serializer ง่ายๆจาก Michael Kropat พบได้ที่นี่: คลาสทั่วไปเป็น 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.