Impor file CSV ke struktur data yang diketik dengan kuat di .Net [ditutup]

106

Apa cara terbaik untuk mengimpor file CSV ke dalam struktur data yang diketik dengan kuat?

MattH
sumber
Ini adalah duplikat stackoverflow.com/questions/1103495/…
Mark Meuer
7
Mengingat ini dibuat setahun lebih awal dari 1103495, saya pikir pertanyaan itu adalah duplikat dari pertanyaan ini.
MattH
2
Terima kasih, Matt. Saya hanya mencoba menghubungkan mereka, bukan menunjukkan mana yang lebih dulu. Anda akan melihat bahwa saya memiliki teks yang persis sama pada pertanyaan lain yang menunjuk pada pertanyaan ini. Adakah cara yang lebih baik untuk menyatukan dua pertanyaan?
Mark Meuer

Jawaban:

74

TextFieldParser Microsoft stabil dan mengikuti RFC 4180 untuk file CSV. Jangan menunda Microsoft.VisualBasicnamespace; itu adalah komponen standar dalam .NETFramework, cukup tambahkan referensi ke Microsoft.VisualBasicrakitan global .

Jika Anda mengompilasi untuk Windows (bukan Mono) dan tidak mengantisipasi keharusan mengurai file CSV yang "rusak" (tidak sesuai RFC), maka ini akan menjadi pilihan yang jelas, karena gratis, tidak terbatas, stabil, dan didukung secara aktif, sebagian besar tidak dapat dikatakan untuk FileHelpers.

Lihat juga: Cara: Membaca Dari File Teks Berbatas Koma dalam Visual Basic untuk contoh kode VB.

MarkJ
sumber
2
Sebenarnya tidak ada VB khusus tentang kelas ini selain namespace sayangnya dinamai. Saya pasti akan memilih pustaka ini jika saya hanya membutuhkan parser CSV "sederhana", karena tidak ada yang perlu diunduh, didistribusikan, atau dikhawatirkan secara umum. Untuk itu, saya telah mengedit frase yang berfokus pada VB dari jawaban ini.
Aaronaught
@Aaronaught Saya pikir hasil edit Anda sebagian besar merupakan peningkatan. Meskipun RFC belum tentu berwibawa, karena banyak penulis CSV tidak mematuhinya, misalnya Excel tidak selalu menggunakan koma dalam file "CSV". Juga bukankah jawaban saya sebelumnya sudah mengatakan bahwa kelas dapat digunakan dari C #?
MarkJ
Ini TextFieldParserakan bekerja untuk tab-delimited dan cruft aneh lain yang dihasilkan Excel juga. Saya menyadari bahwa jawaban Anda sebelumnya tidak mengklaim bahwa pustaka itu khusus VB, itu baru saja menurut saya menyiratkan bahwa itu benar-benar dimaksudkan untuk VB, dan tidak dimaksudkan untuk digunakan dari C #, yang menurut saya tidak kasus - ada beberapa kelas yang sangat berguna di MSVB.
Aaronaught
21

Gunakan koneksi 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();
Kevin
sumber
Ini membutuhkan akses sistem file. Sejauh yang saya tahu tidak ada cara untuk membuat OLEDB berfungsi dengan aliran dalam memori :(
UserControl
3
@UserControl, tentu saja ini memerlukan akses sistem file. Dia bertanya tentang mengimpor file CSV
Kevin
1
Saya tidak mengeluh. Sebenarnya saya lebih suka solusi OLEDB daripada yang lain tetapi saya sering kali frustrasi ketika perlu mengurai CSV dalam aplikasi ASP.NET jadi ingin mencatatnya.
UserControl
12

Jika Anda mengharapkan skenario yang cukup rumit untuk penguraian CSV, jangan pernah berpikir untuk menggulung parser kami sendiri . Ada banyak alat luar biasa di luar sana, seperti FileHelpers , atau bahkan dari CodeProject .

Intinya adalah ini adalah masalah yang cukup umum dan Anda dapat bertaruh bahwa banyak pengembang perangkat lunak telah memikirkan dan memecahkan masalah ini.

Jon Limjap
sumber
Meskipun tautan ini mungkin menjawab pertanyaan, lebih baik menyertakan bagian penting dari jawaban di sini dan menyediakan tautan untuk referensi. Jawaban link saja bisa menjadi tidak valid jika halaman tertaut berubah. - Dari Ulasan
techspider
Terima kasih @techspider Saya harap Anda memperhatikan bahwa posting ini berasal dari periode beta StackOverflow: D Yang sedang dikatakan saat ini alat CSV lebih baik bersumber dari paket Nuget - jadi saya tidak yakin apakah jawaban tautan pun kebal dari 8 tahun -siklus evolusi teknologi yang lama
Jon Limjap
9

Brian memberikan solusi yang bagus untuk mengubahnya menjadi koleksi yang diketik dengan kuat.

Sebagian besar metode penguraian CSV yang diberikan tidak memperhitungkan bidang pelolosan atau beberapa seluk-beluk file CSV lainnya (seperti bidang pemangkasan). Berikut adalah kode yang saya gunakan secara pribadi. Ini agak kasar di sekitar tepinya dan hampir tidak ada pelaporan kesalahan.

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

Perhatikan bahwa ini tidak menangani kasus tepi bidang yang tidak dihilangkan oleh tanda kutip ganda, tetapi meerley memiliki string yang dikutip di dalamnya. Lihat posting ini untuk sedikit penjelasan yang lebih baik serta beberapa tautan ke beberapa perpustakaan yang tepat.

ICR
sumber
9

Saya setuju dengan @ NotMyself . FileHelpers telah teruji dengan baik dan menangani semua jenis kasus tepi yang pada akhirnya harus Anda tangani jika Anda melakukannya sendiri. Lihatlah apa yang FileHelpers lakukan dan hanya tulis sendiri jika Anda benar-benar yakin bahwa (1) Anda tidak akan pernah perlu menangani kasus edge yang dilakukan FileHelpers, atau (2) Anda suka menulis hal semacam ini dan akan melakukannya sangat senang ketika Anda harus mengurai hal-hal seperti ini:

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

2, 'Drake,', 'O'Malley', "Petugas kebersihan,

Ups, saya tidak dikutip dan saya berada di jalur baru!

Jon Galloway
sumber
6

Saya bosan jadi saya memodifikasi beberapa hal yang saya tulis. Ini mencoba untuk merangkum parsing dengan cara OO sementara mengurangi jumlah iterasi melalui file, itu hanya iterasi sekali di bagian depan atas.

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]),
                };
            }
        }
    }
}
Brian Leahy
sumber
2

Cara sederhana yang baik untuk melakukannya adalah dengan membuka file, dan membaca setiap baris ke dalam array, daftar tertaut, struktur data pilihan Anda. Berhati-hatilah saat menangani baris pertama.

Ini mungkin tidak masuk akal, tetapi tampaknya ada cara langsung untuk mengaksesnya juga dengan menggunakan string koneksi .

Mengapa tidak mencoba menggunakan Python, bukan C # atau VB? Ini memiliki modul CSV yang bagus untuk diimpor yang melakukan semua pekerjaan berat untuk Anda.

helloandre
sumber
1
Jangan melompat ke python dari VB demi parser CSV. Ada satu di VB. Meski anehnya tampaknya hal itu diabaikan dalam jawaban atas pertanyaan ini. msdn.microsoft.com/en-us/library/…
MarkJ
1

Saya harus menggunakan parser CSV di .NET untuk proyek musim panas ini dan memilih Microsoft Jet Text Driver. Anda menentukan folder menggunakan string koneksi, lalu membuat kueri file menggunakan pernyataan SQL Select. Anda dapat menentukan tipe yang kuat menggunakan file schema.ini. Saya tidak melakukan ini pada awalnya, tetapi kemudian saya mendapatkan hasil yang buruk di mana jenis datanya tidak langsung terlihat, seperti nomor IP atau entri seperti "XYQ 3.9 SP1".

Satu batasan yang saya temui adalah tidak dapat menangani nama kolom di atas 64 karakter; itu memotong. Ini seharusnya tidak menjadi masalah, kecuali saya berurusan dengan data masukan yang dirancang dengan sangat buruk. Ia mengembalikan ADO.NET DataSet.

Ini adalah solusi terbaik yang saya temukan. Saya akan berhati-hati dalam menjalankan pengurai CSV saya sendiri, karena saya mungkin akan melewatkan beberapa kasus akhir, dan saya tidak menemukan paket penguraian CSV gratis lainnya untuk .NET di luar sana.

EDIT: Juga, hanya ada satu file schema.ini per direktori, jadi saya secara dinamis menambahkannya untuk mengetik kolom yang dibutuhkan. Ini hanya akan mengetik kuat kolom yang ditentukan, dan menyimpulkan untuk setiap bidang yang tidak ditentukan. Saya sangat menghargai ini, karena saya berurusan dengan mengimpor CSV kolom 70+ yang lancar dan tidak ingin menentukan setiap kolom, hanya kolom yang berperilaku tidak semestinya.

pbh101
sumber
Mengapa tidak VB.NET dibangun di parser CSV? msdn.microsoft.com/en-us/library/…
MarkJ
1

Saya mengetik beberapa kode. Hasil di datagridviewer terlihat bagus. Ini mem-parsing satu baris teks ke daftar objek.

    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;
    }
Pieter
sumber
0

Jika Anda dapat menjamin bahwa tidak ada koma dalam data, cara termudah mungkin adalah dengan menggunakan String.split .

Sebagai contoh:

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

Mungkin ada perpustakaan yang bisa Anda gunakan untuk membantu, tapi itu mungkin sesederhana yang bisa Anda dapatkan. Pastikan Anda tidak memiliki koma dalam data, jika tidak, Anda perlu menguraikannya dengan lebih baik.

Mike Stone
sumber
ini bukan solusi optimal
roundcrisis
sangat buruk pada penggunaan memori dan banyak overhead. Kecil harus kurang, terima kasih beberapa kilobyte. Jelas tidak bagus untuk csv 10mb!
Piotr Kula
Itu tergantung pada ukuran memori Anda dan file.
tonymiao