Membaca file CSV menggunakan C #

169

Saya sedang menulis aplikasi impor sederhana dan perlu membaca file CSV, menunjukkan hasil dalam DataGriddan menunjukkan baris file CSV yang rusak di grid lain. Misalnya, tunjukkan garis yang lebih pendek dari 5 nilai di kisi lain. Saya mencoba melakukan itu seperti ini:

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

tetapi sangat sulit untuk beroperasi pada array dalam hal ini. Apakah ada cara yang lebih baik untuk membagi nilai?

ilkin
sumber
Terima kasih atas solusinya. Pertimbangkan mempostingnya sebagai posting jawaban - memasukkannya dalam pertanyaan tidak membantu keterbacaannya.
BartoszKP

Jawaban:

363

Jangan menemukan kembali roda. Manfaatkan apa yang sudah ada di .NET BCL.

  • tambahkan referensi ke Microsoft.VisualBasic(ya, ia mengatakan VisualBasic tetapi bekerja di C # juga - ingat bahwa pada akhirnya itu semua hanya IL)
  • gunakan Microsoft.VisualBasic.FileIO.TextFieldParserkelas untuk mem-parsing file CSV

Berikut ini contoh kode:

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

Ini bekerja sangat baik untuk saya di proyek C # saya.

Berikut ini beberapa tautan / informasi:

David Pokluda
sumber
18
Saya BENAR-BENAR berharap ada cara yang tidak menggunakan perpustakaan VB, tetapi ini bekerja dengan sempurna! Terima kasih!
gillonba
5
+1: Saya baru saja memecahkan lumenworks Fast CSV reader pada file 53Mb. Sepertinya caching baris gagal setelah 43.000 baris dan mengacak buffer. TextFieldParserSudah mencoba VB dan berhasil. Terima kasih
Lewat Coding
10
+1 Jawaban yang bagus, karena saya temukan banyak orang tidak tahu kelas ini ada. Satu hal yang perlu diperhatikan oleh pemirsa di masa depan adalah bahwa pengaturan parser.TextFieldType = FieldType.Delimited;tidak diperlukan jika Anda menelepon parser.SetDelimiters(",");, karena metode ini menentukan TextFieldTypeproperti untuk Anda.
Brian
10
Lihat juga ini: dotnetperls.com/textfieldparser . TextFieldParser memiliki kinerja lebih buruk daripada String.Split dan StreamReader. Namun, ada perbedaan besar antara string.Split dan TextFieldParser. TextFieldParser menangani kasus aneh seperti memiliki koma di kolom: Anda dapat memberi nama kolom seperti "text with quote"", and comma", dan Anda bisa mendapatkan nilai yang benar text with quote", and commadaripada nilai yang dipisahkan secara salah. Jadi, Anda mungkin ingin memilih String.Split jika Anda csv sangat sederhana.
Yongwei Wu
5
Perhatikan bahwa Anda mungkin perlu menambahkan referensi ke Microsoft.VisualBasic untuk menggunakan ini. Klik kanan pada proyek Anda di Visual Studio, lalu pilih Tambah> Referensi, dan centang kotak untuk Microsoft.VisualBasic.
Derek Kurth
37

Pengalaman saya adalah bahwa ada banyak format csv yang berbeda. Khususnya bagaimana mereka menangani pelolosan kutipan dan pembatas dalam suatu bidang.

Ini adalah varian yang saya temui:

  • kutipan dikutip dan digandakan (excel) yaitu 15 "-> field1," 15 "" ", field3
  • tanda kutip tidak diubah kecuali bidang tersebut dikutip karena alasan lain. yaitu 15 "-> bidang1,15", bidang3
  • kutipan diloloskan dengan \. yaitu 15 "-> field1," 15 \ "", field3
  • kutipan tidak berubah sama sekali (ini tidak selalu mungkin untuk menguraikan dengan benar)
  • pembatas dikutip (excel). yaitu a, b -> field1, "a, b", field3
  • pembatas lolos dengan \. yaitu a, b -> field1, a \, b, field3

Saya telah mencoba banyak parser csv yang ada tetapi tidak ada satu pun yang dapat menangani varian yang saya temui. Juga sulit untuk mencari tahu dari dokumentasi yang lolos dari varian yang didukung parser.

Dalam proyek saya sekarang saya menggunakan VB TextFieldParser atau splitter kustom.

adrianma
sumber
1
Sukai jawaban ini untuk kasus uji yang Anda berikan!
Matius Rodatus
2
Masalah utama adalah bahwa sebagian besar implementasi tidak peduli tentang RFC 4180 yang menjelaskan format CSV dan bagaimana pembatas harus diloloskan.
Jenny O'Reilly
RFC-4180 berasal dari 2005, yang tampaknya sudah tua sekarang, tapi ingat: .Net framework pertama kali keluar pada tahun 2001. Juga, RFC tidak selalu standar resmi, dan dalam hal ini tidak membawa bobot yang sama seperti, katakanlah , ISO-8601 atau RFC-761.
Joel Coehoorn
23

Saya merekomendasikan CsvHelper dari Nuget .

(Menambahkan referensi ke Microsoft.VisualBasic hanya merasa tidak benar, itu tidak hanya jelek, itu mungkin bahkan tidak lintas platform.)

knocte
sumber
2
Persis seperti lintas-platform seperti C #.
PRMan
salah, Microsoft.VisualBasic.dll di Linux berasal dari sumber Mono, yang memiliki implementasi yang berbeda dari Microsoft dan ada beberapa hal yang tidak diimplementasikan, misalnya: stackoverflow.com/questions/6644165/…
knocte
(Plus, bahasa VB tidak pernah memiliki fokus di bawah perusahaan yang telah terlibat dalam menciptakan / mengembangkan proyek Mono, jadi itu jauh di belakang dalam hal upaya, dibandingkan dengan ekosistem C # / tooling.)
knocte
1
Setelah bermain dengan keduanya, saya akan menambahkan yang CsvHelperdilengkapi dengan baris bawaan untuk class mapper; itu memungkinkan untuk variasi dalam tajuk kolom (jika ada), dan bahkan variasi dalam tatanan kolom (meskipun saya sendiri belum menguji yang terakhir). Semua itu terasa jauh lebih "tingkat tinggi" daripada TextFieldParser.
David
1
yup, Microsoft.Visual namespace
Dasar
13

Kadang-kadang menggunakan perpustakaan itu keren ketika Anda tidak ingin menemukan kembali roda, tetapi dalam hal ini orang dapat melakukan pekerjaan yang sama dengan lebih sedikit baris kode dan lebih mudah dibaca dibandingkan dengan menggunakan perpustakaan. Ini adalah pendekatan berbeda yang menurut saya sangat mudah digunakan.

  1. Dalam contoh ini, saya menggunakan StreamReader untuk membaca file
  2. Regex untuk mendeteksi pembatas dari setiap baris.
  3. Array untuk mengumpulkan kolom dari indeks 0 hingga 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 */
        }
    }
Mana
sumber
4
Tentunya ada masalah dengan data yang berisi baris baru?
Doogal
Sekarang file data CSV tidak tahu untuk berisi baris kosong antara data, tetapi jika Anda memiliki sumber yang melakukan itu, dalam hal ini saya hanya akan melakukan tes regex sederhana untuk menghapus spasi putih atau garis yang tidak mengandung apa pun sebelum menjalankan pembaca. periksa di sini untuk contoh berbeda: stackoverflow.com/questions/7647716/…
Mana
1
Tentunya pendekatan berbasis char lebih alami untuk masalah semacam ini daripada regex. Tergantung pada keberadaan tanda kutip, perilaku tersebut seharusnya berbeda.
Casey
6

CSV bisa rumit nyata cepat.

Gunakan sesuatu yang kuat dan teruji dengan baik:
FileHelpers: www.filehelpers.net

FileHelpers adalah .NET library gratis dan mudah digunakan untuk mengimpor / mengekspor data dari panjang tetap atau catatan terbatas dalam file, string atau stream.

Keith Blows
sumber
5
Saya pikir FileHelper berusaha melakukan banyak hal sekaligus. Parsing file adalah proses 2 langkah di mana Anda pertama kali membagi garis menjadi bidang dan kemudian mengurai bidang menjadi data. Menggabungkan fungsi membuatnya sulit untuk menangani hal-hal seperti detail master dan penyaringan garis.
adrianm
4

Satu lagi ke daftar ini, Cinchoo ETL - perpustakaan open source untuk membaca dan menulis file CSV

Untuk contoh file CSV di bawah ini

Id, Name
1, Tom
2, Mark

Dengan cepat Anda dapat memuatnya menggunakan pustaka seperti di bawah ini

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

Jika Anda memiliki kelas POCO yang cocok dengan file CSV

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

Anda dapat menggunakannya untuk memuat file CSV seperti di bawah ini

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

Silakan periksa artikel di CodeProject tentang cara menggunakannya.

Penafian: Saya penulis perpustakaan ini

RajN
sumber
Hai, dapatkah Anda memuat csv ke tabel Sql - Saya tidak tahu header di tabel CSV sebelumnya. Hanya mirror whats in csv ke Sql table
aggie
Ya kamu bisa. silakan lihat tautan ini 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();
                    }
                }

         }
anongf4gsdfg54564533
sumber
dari mana Anda menyalin solusi ini?
MindRoasterMir
0

Pertama-tama perlu memahami apa itu CSV dan bagaimana cara menulisnya.

  1. Setiap string berikutnya ( /r/n) adalah baris "tabel" berikutnya.
  2. Sel "Tabel" dipisahkan oleh beberapa simbol pembatas. Simbol yang paling sering digunakan adalah \tatau,
  3. Setiap sel mungkin mengandung simbol pembatas ini (sel harus mulai dengan simbol tanda kutip dan diakhiri dengan simbol ini dalam kasus ini)
  4. Setiap sel mungkin dapat berisi /r/nsimbol (sel harus mulai dengan simbol tanda kutip dan diakhiri dengan simbol ini dalam kasus ini)

Cara termudah untuk C # / Visual Basic untuk bekerja dengan file CSV adalah dengan menggunakan Microsoft.VisualBasicperpustakaan standar . Anda hanya perlu menambahkan referensi yang diperlukan, dan string berikut ke kelas Anda:

using Microsoft.VisualBasic.FileIO;

Ya, Anda bisa menggunakannya dalam C #, jangan khawatir. Pustaka ini dapat membaca file yang relatif besar dan mendukung semua aturan yang diperlukan, sehingga Anda dapat bekerja dengan semua file CSV.

Beberapa waktu lalu saya menulis kelas sederhana untuk CSV baca / tulis berdasarkan perpustakaan ini. Menggunakan kelas sederhana ini Anda akan dapat bekerja dengan CSV seperti dengan array 2 dimensi. Anda dapat menemukan kelas saya dengan tautan berikut: https://github.com/ukushu/DataExporter

Contoh sederhana menggunakan:

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");
Andrew
sumber
0

Untuk menyelesaikan jawaban sebelumnya, orang mungkin perlu koleksi objek dari File CSV-nya, baik diuraikan oleh TextFieldParseratau string.Splitmetode, dan kemudian setiap baris dikonversi ke objek melalui Refleksi. Anda jelas harus terlebih dahulu mendefinisikan kelas yang cocok dengan garis-garis file CSV.

Saya menggunakan Serializer CSV sederhana dari Michael Kropat yang ditemukan di sini: Kelas generik ke CSV (semua properti) dan menggunakan kembali metodenya untuk mendapatkan bidang dan properti dari kelas yang diinginkan.

Saya deserialize file CSV saya dengan metode berikut:

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

Saya sangat menyarankan menggunakan CsvHelper.

Ini contoh singkatnya:

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

Dokumentasi lengkap dapat ditemukan di: https://joshclose.github.io/CsvHelper

Kieran
sumber