Nhập tệp CSV vào cấu trúc dữ liệu được nhập mạnh trong .Net [đóng]


106

Cách tốt nhất để nhập tệp CSV vào cấu trúc dữ liệu được đánh máy mạnh là gì?




7
Xem xét câu hỏi này được tạo ra một năm sớm hơn năm 1103495, tôi nghĩ rằng câu hỏi đó là một bản sao của câu hỏi này.
MattH

2
Cảm ơn, Matt. Tôi chỉ cố gắng liên kết chúng với nhau, không chỉ ra cái nào đến trước. Bạn sẽ thấy tôi có nội dung chính xác trong câu hỏi khác chỉ vào câu hỏi này. Có cách nào tốt hơn để kết hợp hai câu hỏi với nhau không?
Mark Meuer

Câu trả lời:


74

TextFieldParser của Microsoft ổn định và tuân theo RFC 4180 cho các tệp CSV. Không được đặt ra bởi Microsoft.VisualBasickhông gian tên; nó là một thành phần tiêu chuẩn trong .NET Framework, chỉ cần thêm một tham chiếu đến toàn cục Microsoft.VisualBasic.

Nếu bạn đang biên dịch cho Windows (trái ngược với Mono) và không dự đoán sẽ phải phân tích cú pháp các tệp CSV "bị hỏng" (không tuân thủ RFC), thì đây sẽ là lựa chọn hiển nhiên, vì nó miễn phí, không hạn chế, ổn định, và được hỗ trợ tích cực, hầu hết trong số đó không thể không nói đến FileHelpers.

Xem thêm: Cách: Đọc Từ Tệp Văn bản Phân cách bằng Dấu phẩy trong Visual Basic để biết ví dụ về mã VB.


2
Thực ra không có gì VB cụ thể về lớp này ngoài không gian tên được đặt tên đáng tiếc của nó. Tôi chắc chắn sẽ chọn thư viện này nếu tôi chỉ cần một trình phân tích cú pháp CSV "đơn giản", bởi vì nói chung không có gì để tải xuống, phân phối hoặc lo lắng. Cuối cùng, tôi đã chỉnh sửa cụm từ tập trung vào VB của câu trả lời này.
Aaronaught

@Aaronaught Tôi nghĩ rằng các chỉnh sửa của bạn chủ yếu là một cải tiến. Mặc dù RFC đó không nhất thiết phải có thẩm quyền, vì nhiều người viết CSV không tuân thủ nó, ví dụ như Excel không phải lúc nào cũng sử dụng dấu phẩy trong tệp "CSV". Cũng không phải câu trả lời trước của tôi đã nói rằng lớp có thể được sử dụng từ C #?
MarkJ

TextFieldParsercũng sẽ hoạt động đối với các dấu hiệu được phân cách bằng tab và các dấu hiệu lạ khác do Excel tạo ra. Tôi nhận ra rằng câu trả lời trước của bạn đã không tuyên bố rằng thư viện là VB cụ thể, nó chỉ đi qua với tôi như ngụ ý rằng nó đã thực sự có ý nghĩa đối với VB, và không có ý định để được sử dụng từ C #, mà tôi không nghĩ là trường hợp - có một số lớp thực sự hữu ích trong MSVB.
Aaronaught

21

Sử dụng kết nối 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();

Điều này yêu cầu quyền truy cập hệ thống tệp. Theo như tôi biết không có cách nào để làm cho OLEDB làm việc với những con suối trong bộ nhớ :(
UserControl

3
@UserControl, tất nhiên nó yêu cầu quyền truy cập hệ thống tệp. Anh ấy hỏi về việc nhập tệp CSV
Kevin

1
Tôi không phàn nàn. Trên thực tế, tôi thích giải pháp OLEDB hơn phần còn lại nhưng tôi đã rất nhiều lần thất vọng khi cần phân tích cú pháp CSV trong các ứng dụng ASP.NET nên muốn lưu ý nó.
UserControl

12

Nếu bạn đang mong đợi các tình huống khá phức tạp cho phân tích cú pháp CSV, thậm chí đừng nghĩ đến việc sử dụng trình phân tích cú pháp của riêng chúng tôi . Có rất nhiều công cụ tuyệt vời như FileHelpers , hoặc thậm chí từ CodeProject .

Vấn đề là đây là một vấn đề khá phổ biến và bạn có thể đặt cược rằng rất nhiều nhà phát triển phần mềm đã nghĩ đến và giải quyết vấn đề này.


Mặc dù liên kết này có thể trả lời câu hỏi, nhưng tốt hơn hết bạn nên đưa các phần thiết yếu của câu trả lời vào đây và cung cấp liên kết để tham khảo. Các câu trả lời chỉ có liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi. - Từ xét
techspider

Cảm ơn @techspider Tôi hy vọng bạn đã lưu ý rằng bài đăng này là từ giai đoạn beta của StackOverflow: D Điều đó đang được nói ngày nay các công cụ CSV có nguồn tốt hơn từ các gói Nuget - vì vậy tôi không chắc liệu ngay cả các câu trả lời liên kết có miễn dịch từ 8 năm hay không chu kỳ tiến hóa -old công nghệ
Jon Limjap

9

Brian đưa ra một giải pháp hay để chuyển nó thành một bộ sưu tập được đánh máy mạnh.

Hầu hết các phương pháp phân tích cú pháp CSV đã cung cấp không tính đến các trường thoát hoặc một số tính chất tinh vi khác của tệp CSV (như trường cắt xén). Đây là mã cá nhân tôi sử dụng. Nó hơi thô xung quanh các cạnh và hầu như không có báo cáo lỗi.

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

Lưu ý rằng điều này không xử lý trường hợp cạnh của các trường không được phân tách bằng dấu ngoặc kép, nhưng meerley có một chuỗi được trích dẫn bên trong nó. Xem bài đăng này để biết rõ hơn một chút cũng như một số liên kết đến một số thư viện thích hợp.


9

Tôi đồng ý với @ NotMyself . FileHelpers được thử nghiệm tốt và xử lý tất cả các loại trường hợp phức tạp mà cuối cùng bạn sẽ phải đối phó nếu bạn tự làm. Hãy xem những gì FileHelpers làm và chỉ viết nội dung của riêng bạn nếu bạn hoàn toàn chắc chắn rằng (1) bạn sẽ không bao giờ cần phải xử lý các trường hợp phức tạp mà FileHelpers làm, hoặc (2) bạn thích viết loại nội dung này và sẽ vui mừng khôn xiết khi bạn phải phân tích cú pháp như thế này:

1, "Bill", "Smith", "Supervisor", "No Comment"

2, 'Drake,', 'O'Malley', "Người gác cổng,

Rất tiếc, tôi không được trích dẫn và tôi đang ở một dòng mới!


6

Tôi cảm thấy buồn chán vì vậy tôi đã sửa đổi một số thứ mà tôi đã viết. Nó cố gắng đóng gói phân tích cú pháp theo cách OO với điều kiện cắt giảm số lần lặp lại qua tệp, nó chỉ lặp lại một lần ở phần trên cùng.

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

Một cách đơn giản hay để làm điều đó là mở tệp và đọc từng dòng vào một mảng, danh sách được liên kết, cấu trúc dữ liệu do bạn lựa chọn. Hãy cẩn thận về việc xử lý dòng đầu tiên.

Điều này có thể vượt qua tầm nhìn của bạn, nhưng dường như có một cách trực tiếp để truy cập chúng cũng như sử dụng một chuỗi kết nối .

Tại sao không thử sử dụng Python thay vì C # hoặc VB? Nó có một mô-đun CSV tuyệt vời để nhập, thực hiện tất cả các công việc nặng nhọc cho bạn.


1
Đừng chuyển sang python từ VB vì lợi ích của trình phân tích cú pháp CSV. Có một trong VB. Mặc dù kỳ lạ nó dường như đã bị bỏ qua trong các câu trả lời cho câu hỏi này. msdn.microsoft.com/en-us/library/…
MarkJ

1

Tôi đã phải sử dụng trình phân tích cú pháp CSV trong .NET cho một dự án vào mùa hè này và giải quyết bằng Microsoft Jet Text Driver. Bạn chỉ định một thư mục bằng cách sử dụng chuỗi kết nối, sau đó truy vấn tệp bằng câu lệnh SQL Select. Bạn có thể chỉ định loại mạnh bằng tệp schema.ini. Tôi đã không làm điều này lúc đầu, nhưng sau đó tôi nhận được kết quả không tốt khi loại dữ liệu không rõ ràng ngay lập tức, chẳng hạn như số IP hoặc mục nhập như "XYQ 3.9 SP1".

Một hạn chế mà tôi gặp phải là nó không thể xử lý các tên cột trên 64 ký tự; nó cắt ngắn. Đây không phải là vấn đề, ngoại trừ tôi đang xử lý dữ liệu đầu vào được thiết kế rất kém. Nó trả về một Tập dữ liệu ADO.NET.

Đây là giải pháp tốt nhất mà tôi tìm thấy. Tôi sẽ cảnh giác với việc sử dụng trình phân tích cú pháp CSV của riêng mình, vì tôi có thể sẽ bỏ lỡ một số trường hợp cuối và tôi không tìm thấy bất kỳ gói phân tích cú pháp CSV miễn phí nào khác cho .NET ngoài đó.

CHỈNH SỬA: Ngoài ra, chỉ có thể có một tệp schema.ini cho mỗi thư mục, vì vậy tôi tự động thêm vào nó để gõ mạnh các cột cần thiết. Nó sẽ chỉ nhập mạnh các cột được chỉ định và suy ra cho bất kỳ trường không xác định nào. Tôi thực sự đánh giá cao điều này, vì tôi đang xử lý việc nhập CSV hơn 70 cột linh hoạt và không muốn chỉ định từng cột, chỉ những cột hoạt động sai.


Tại sao VB.NET không được xây dựng trong trình phân tích cú pháp CSV? msdn.microsoft.com/en-us/library/…
MarkJ

1

Tôi đã nhập một số mã. Kết quả trong trình xem dữ liệu có vẻ tốt. Nó phân tích cú pháp một dòng văn bản thành một danh sách mảng các đối tượng.

    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

Nếu bạn có thể đảm bảo rằng không có dấu phẩy trong dữ liệu, thì cách đơn giản nhất có thể là sử dụng String.split .

Ví dụ:

String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);

Có thể có thư viện bạn có thể sử dụng để trợ giúp, nhưng điều đó có thể đơn giản nhất bạn có thể làm được. Chỉ cần đảm bảo rằng bạn không thể có dấu phẩy trong dữ liệu, nếu không, bạn sẽ cần phải phân tích cú pháp tốt hơn.


đây không phải là một giải pháp tối ưu
roundcrisis

rất tệ về việc sử dụng bộ nhớ và rất nhiều chi phí. Nhỏ nên ít cảm ơn một vài kilobyte. Chắc chắn không tốt cho một csv 10mb!
Piotr Kula

Nó phụ thuộc vào dung lượng bộ nhớ của bạn và tệp.
tonymiao
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.