Đọc tệp CSV bằng C #


169

Tôi đang viết một ứng dụng nhập đơn giản và cần đọc tệp CSV, hiển thị kết quả trong DataGridvà hiển thị các dòng bị hỏng của tệp CSV trong một lưới khác. Ví dụ: hiển thị các dòng ngắn hơn 5 giá trị trong lưới khác. Tôi đang cố gắng làm điều đó như thế này:

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

nhưng rất khó để hoạt động trên mảng trong trường hợp này. Có cách nào tốt hơn để phân chia các giá trị?


Cảm ơn giải pháp của bạn. Xem xét việc đăng nó dưới dạng bài đăng câu trả lời - bao gồm nó trong câu hỏi không giúp ích cho khả năng đọc của nó.
BartoszKP

Câu trả lời:


363

Đừng phát minh lại bánh xe. Tận dụng những gì đã có trong .NET BCL.

  • thêm một tham chiếu đến Microsoft.VisualBasic(vâng, nó nói VisualBasic nhưng nó cũng hoạt động trong C # - hãy nhớ rằng cuối cùng tất cả chỉ là IL)
  • sử dụng Microsoft.VisualBasic.FileIO.TextFieldParserlớp để phân tích tệp CSV

Đây là mã mẫu:

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

Nó hoạt động rất tốt cho tôi trong các dự án C # của tôi.

Dưới đây là một số liên kết / thông tin khác:


18
Tôi thực sự muốn có một cách không sử dụng các thư viện VB, nhưng cách này hoạt động hoàn hảo! Cảm ơn bạn!
gillonba

5
+1: Tôi vừa phá vỡ trình đọc CSV nhanh của lumworks trên tệp 53Mb. Có vẻ như bộ nhớ đệm dòng không thành công sau 43.000 hàng và làm xáo trộn bộ đệm. Đã thử VB TextFieldParservà nó đã lừa. Cảm ơn
Mã hóa

10
+1 Câu trả lời tuyệt vời, vì tôi thấy nhiều người không biết lớp này tồn tại. Một điều mà người xem trong tương lai cần lưu ý là cài đặt parser.TextFieldType = FieldType.Delimited;là không cần thiết nếu bạn gọi parser.SetDelimiters(",");, vì phương thức đặt thuộc TextFieldTypetính cho bạn.
Brian

10
Ngoài ra kiểm tra này: dotnetperls.com/textfieldparser . TextFieldParser có hiệu năng kém hơn String.Split và StreamReader. Tuy nhiên, có một sự khác biệt lớn giữa string.Split và TextFieldParser. TextFieldParser xử lý các trường hợp lạ như có dấu phẩy trong một cột: bạn có thể đặt tên cho một cột như thế nào "text with quote"", and comma"và bạn có thể nhận được giá trị chính xác text with quote", and commathay vì các giá trị được phân tách sai. Vì vậy, bạn có thể muốn chọn String.Split nếu bạn csv rất đơn giản.
Yongwei Wu

5
Lưu ý rằng bạn có thể cần thêm một tham chiếu đến Microsoft.VisualBasic để sử dụng điều này. Nhấp chuột phải vào dự án của bạn trong Visual Studio, sau đó chọn Thêm> Tham khảo và chọn hộp cho Microsoft.VisualBasic.
Derek Kurth

37

Kinh nghiệm của tôi là có nhiều định dạng csv khác nhau. Đặc biệt là cách họ xử lý thoát dấu ngoặc kép và dấu phân cách trong một trường.

Đây là những biến thể tôi đã chạy vào:

  • trích dẫn được trích dẫn và nhân đôi (excel) tức là 15 "-> field1," 15 "" ", field3
  • trích dẫn không được thay đổi trừ khi trường được trích dẫn vì một số lý do khác. tức là 15 "-> trường1,15", các trường3
  • trích dẫn được thoát với \. tức là 15 "-> trường1," 15 \ "", trường3
  • trích dẫn hoàn toàn không thay đổi (điều này không phải lúc nào cũng có thể phân tích chính xác)
  • dấu phân cách được trích dẫn (excel). tức là a, b -> trường1, "a, b", trường3
  • dấu phân cách được thoát với \. tức là a, b -> trường1, a \, b, trường3

Tôi đã thử nhiều trình phân tích cú pháp csv hiện có nhưng không có một trình phân tích duy nhất nào có thể xử lý các biến thể mà tôi đã chạy vào. Cũng rất khó để tìm ra từ tài liệu thoát các biến thể mà trình phân tích cú pháp hỗ trợ.

Trong các dự án của tôi, bây giờ tôi sử dụng VB TextFieldParser hoặc bộ chia tùy chỉnh.


1
Yêu câu trả lời này cho các trường hợp thử nghiệm bạn cung cấp!
Matthew Rodatus

2
Vấn đề chính là hầu hết các triển khai không quan tâm đến RFC 4180 mô tả định dạng CSV và cách thoát dấu phân cách.
Jenny O'Reilly

RFC-4180 là từ năm 2005, có vẻ như đã cũ, nhưng hãy nhớ rằng: khung .Net lần đầu tiên ra mắt vào năm 2001. Ngoài ra, RFC không phải lúc nào cũng là tiêu chuẩn chính thức, và trong trường hợp này, nó không mang cùng trọng lượng như, nói , ISO-8601 hoặc RFC-761.
Joel Coehoorn

23

Tôi giới thiệu CsvHelper từ Nuget .

(Thêm một tham chiếu đến Microsoft.VisualBasic không cảm thấy đúng, nó không chỉ xấu, thậm chí có thể không phải là đa nền tảng.)


2
Nó chính xác là đa nền tảng như C #.
PRman

sai, Microsoft.VisualBasic.dll trong Linux đến từ các nguồn Mono, có cách triển khai khác với Microsoft và có một số điều không được triển khai, ví dụ: stackoverflow.com/questions/6644165/
trộm

(Thêm vào đó, ngôn ngữ VB chưa bao giờ tập trung vào các công ty đã tham gia vào việc tạo / phát triển dự án Mono, vì vậy nó đi sau về mặt nỗ lực, so với hệ sinh thái / công cụ C #.)
knocte

1
Đã chơi với cả hai, tôi thêm rằng CsvHelperđi kèm với một trình ánh xạ tích hợp cho hàng; nó cho phép các biến thể trong các tiêu đề cột (nếu có) và thậm chí các biến thể rõ ràng trong thứ tự cột (mặc dù bản thân tôi chưa kiểm tra cái sau). Tất cả trong tất cả, nó cảm thấy "cấp cao" hơn nhiều TextFieldParser.
David

1
yup, không gian tên Microsoft.VisualBasic không khả dụng trên .NET Core 2.1
N4ppeL

13

Đôi khi sử dụng thư viện rất tuyệt khi bạn không muốn phát minh lại bánh xe, nhưng trong trường hợp này, người ta có thể thực hiện cùng một công việc với ít dòng mã hơn và dễ đọc hơn so với sử dụng thư viện. Đây là một cách tiếp cận khác nhau mà tôi thấy rất dễ sử dụng.

  1. Trong ví dụ này, tôi sử dụng StreamReader để đọc tệp
  2. Regex để phát hiện dấu phân cách từ mỗi dòng.
  3. Một mảng để thu thập các cột từ chỉ số 0 đến 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
Chắc chắn là có vấn đề với dữ liệu mà chính nó chứa các dòng mới?
Doogal

Bây giờ các tệp dữ liệu CSV không biết có chứa các dòng trống giữa dữ liệu, nhưng nếu bạn có một nguồn thực hiện điều đó, trong trường hợp đó tôi sẽ chỉ thực hiện một thử nghiệm regex đơn giản để loại bỏ khoảng trắng hoặc dòng không chứa gì trước khi chạy trình đọc. kiểm tra ở đây để biết các ví dụ khác nhau: stackoverflow.com/questions/7647716/ từ
Mana

1
Chắc chắn một cách tiếp cận dựa trên char là tự nhiên cho loại vấn đề này hơn là một regex. Tùy thuộc vào sự hiện diện của dấu ngoặc kép, hành vi được cho là khác nhau.
Casey

6

CSV có thể nhận được thực sự phức tạp nhanh chóng.

Sử dụng một cái gì đó mạnh mẽ và được kiểm tra tốt:
FileHelpers: www.filehelpers.net

FileHelpers là một thư viện .NET miễn phí và dễ sử dụng để nhập / xuất dữ liệu từ các bản ghi có độ dài cố định hoặc được phân tách trong các tệp, chuỗi hoặc luồng.


5
Tôi nghĩ FileHelper đang cố gắng làm nhiều việc trong một lần. Phân tích tệp là một quá trình gồm 2 bước, trước tiên bạn chia dòng thành các trường và sau đó phân tích các trường thành dữ liệu. Kết hợp các chức năng gây khó khăn cho việc xử lý những thứ như chi tiết tổng thể và lọc dòng.
adrianm


4

Một cái khác trong danh sách này, Cinchoo ETL - một thư viện mã nguồn mở để đọc và ghi các tệp CSV

Đối với tệp CSV mẫu bên dưới

Id, Name
1, Tom
2, Mark

Nhanh chóng bạn có thể tải chúng bằng thư viện như dưới đây

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

Nếu bạn có lớp POCO khớp với tệp CSV

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

Bạn có thể sử dụng nó để tải tệp CSV như dưới đây

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

Vui lòng kiểm tra các bài viết tại CodeProject về cách sử dụng nó.

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của thư viện này


Xin chào, bạn có thể tải csv vào bảng Sql không - Tôi không biết tiêu đề trong bảng CSV trước khi sử dụng. Chỉ cần phản chiếu những gì trong bảng csv sang Sql
aggie

Vâng, bạn có thể. vui lòng xem liên kết này stackoverflow.com/questions/20759302/
Kẻ

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();
                    }
                }

         }

bạn đã sao chép giải pháp này từ đâu?
MindRoasterMir

0

Trước hết cần hiểu CSV là gì và cách viết nó.

  1. Mỗi chuỗi tiếp theo ( /r/n) là hàng "bảng" tiếp theo.
  2. Các ô "Bảng" được phân tách bằng một số ký hiệu dấu phân cách. Biểu tượng thường được sử dụng là \thoặc,
  3. Mọi ô có thể chứa biểu tượng dấu phân cách này (ô phải bắt đầu bằng ký hiệu dấu ngoặc kép và kết thúc bằng ký hiệu này trong trường hợp này)
  4. Mỗi ô có thể chứa các /r/nbiểu tượng (ô phải bắt đầu bằng ký hiệu dấu ngoặc kép và kết thúc bằng ký hiệu này trong trường hợp này)

Cách dễ nhất để C # / Visual Basic hoạt động với các tệp CSV là sử dụng Microsoft.VisualBasicthư viện chuẩn . Bạn chỉ cần thêm tham chiếu cần thiết và chuỗi sau vào lớp của bạn:

using Microsoft.VisualBasic.FileIO;

Có, bạn có thể sử dụng nó trong C #, đừng lo lắng. Thư viện này có thể đọc các tệp tương đối lớn và hỗ trợ tất cả các quy tắc cần thiết, vì vậy bạn sẽ có thể làm việc với tất cả các tệp CSV.

Cách đây một thời gian, tôi đã viết một lớp đơn giản để đọc / ghi CSV dựa trên thư viện này. Sử dụng lớp đơn giản này, bạn sẽ có thể làm việc với CSV như với mảng 2 chiều. Bạn có thể tìm thấy lớp học của tôi bằng liên kết sau: https://github.com/ukushu/DataExporter

Ví dụ đơn giản về việc sử dụng:

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

Để hoàn thành các câu trả lời trước đó, người ta có thể cần một bộ sưu tập các đối tượng từ Tệp CSV của mình, được phân tích bằng phương thức TextFieldParserhoặc string.Splitphương thức, sau đó mỗi dòng được chuyển đổi thành một đối tượng thông qua Reflection. Trước tiên, bạn cần xác định một lớp khớp với các dòng của tệp CSV.

Tôi đã sử dụng Trình tuần tự CSV đơn giản từ Michael Kropat được tìm thấy ở đây: Lớp chung cho CSV (tất cả các thuộc tính) và sử dụng lại các phương thức của anh ấy để lấy các trường và thuộc tính của lớp mong muốn.

Tôi giải tuần tự hóa tệp CSV của mình bằng phương pháp sau:

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

Tôi rất khuyên bạn nên sử dụng CsvHelper.

Đây là một ví dụ nhanh:

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>();
}

Tài liệu đầy đủ có thể được tìm thấy tại: https://joshclose.github.io/CsvHelper

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.