c # datatable ke csv


113

Bisakah seseorang memberi tahu saya mengapa kode berikut tidak berfungsi. Data tersebut disimpan ke dalam file csv, namun datanya tidak dipisahkan. Semuanya ada dalam sel pertama setiap baris.

StringBuilder sb = new StringBuilder();

foreach (DataColumn col in dt.Columns)
{
    sb.Append(col.ColumnName + ',');
}

sb.Remove(sb.Length - 1, 1);
sb.Append(Environment.NewLine);

foreach (DataRow row in dt.Rows)
{
    for (int i = 0; i < dt.Columns.Count; i++)
    {
        sb.Append(row[i].ToString() + ",");
    }

    sb.Append(Environment.NewLine);
}

File.WriteAllText("test.csv", sb.ToString());

Terima kasih.



Saya mengembangkan Ekstensi kinerja tinggi. periksa jawaban ini
Nigje

Jawaban:


229

Versi pendek berikut terbuka dengan baik di Excel, mungkin masalah Anda adalah tanda koma

.net = 3.5

StringBuilder sb = new StringBuilder(); 

string[] columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName).
                                  ToArray();
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    string[] fields = row.ItemArray.Select(field => field.ToString()).
                                    ToArray();
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

.net> = 4.0

Dan seperti yang Tim tunjukkan, jika Anda menggunakan .net> = 4, Anda dapat membuatnya lebih pendek:

StringBuilder sb = new StringBuilder(); 

IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                  Select(column => column.ColumnName);
sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in dt.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => field.ToString());
    sb.AppendLine(string.Join(",", fields));
}

File.WriteAllText("test.csv", sb.ToString());

Seperti yang disarankan oleh Christian, jika Anda ingin menangani karakter khusus yang keluar dalam bidang, ganti blok loop dengan:

foreach (DataRow row in dt.Rows)
{
    IEnumerable<string> fields = row.ItemArray.Select(field => 
      string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
    sb.AppendLine(string.Join(",", fields));
}

Dan saran terakhir, Anda bisa menulis baris demi baris konten csv daripada sebagai keseluruhan dokumen, untuk menghindari dokumen besar di memori.


2
Tidak perlu menyalin ItemArrayke yang baru String[], Anda dapat menghilangkan .ToArray()dengan .NET 4 dan menggunakan String.Joinkelebihan beban yang membutuhkan IEnumerable<T>(diedit).
Tim Schmelter

2
@TimSchmelter, ya tapi kelebihan beban ini diperkenalkan di .net4, kode tidak akan dikompilasi jika OP menggunakan .net <4
vc 74

18
Metode ini tidak memperhitungkan koma di dalam nilai kolom.
Kristen

2
Sebagai gantinya, IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", ladang) + "\" ");
Kristen

2
@ Si8 Apa maksudmu? Jawaban ini hanya menggunakan komponen db, dan &nbspbiasanya digunakan untuk dokumen HTML / XML. Bukan kode di atas yang memproduksinya kecuali tabel berisi &nbsp;secara eksplisit
vc 74

36

Saya membungkus ini menjadi kelas ekstensi, yang memungkinkan Anda untuk memanggil:

myDataTable.WriteToCsvFile("C:\\MyDataTable.csv");

di DataTable mana pun.

public static class DataTableExtensions 
{
    public static void WriteToCsvFile(this DataTable dataTable, string filePath) 
    {
        StringBuilder fileContent = new StringBuilder();

        foreach (var col in dataTable.Columns) 
        {
            fileContent.Append(col.ToString() + ",");
        }

        fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) 
        {
            foreach (var column in dr.ItemArray) 
            {
                fileContent.Append("\"" + column.ToString() + "\",");
            }

            fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1);
        }

        System.IO.File.WriteAllText(filePath, fileContent.ToString());
    }
}

24

Fungsi ekstensi baru berdasarkan jawaban Paul Grimshaw. Saya membersihkannya dan menambahkan kemampuan untuk menangani data yang tidak terduga. (Data Kosong, Kutipan Tersemat, dan koma di judul ...)

Ini juga mengembalikan string yang lebih fleksibel. Ia mengembalikan Null jika objek tabel tidak berisi struktur apa pun.

    public static string ToCsv(this DataTable dataTable) {
        StringBuilder sbData = new StringBuilder();

        // Only return Null if there is no structure.
        if (dataTable.Columns.Count == 0)
            return null;

        foreach (var col in dataTable.Columns) {
            if (col == null)
                sbData.Append(",");
            else
                sbData.Append("\"" + col.ToString().Replace("\"", "\"\"") + "\",");
        }

        sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);

        foreach (DataRow dr in dataTable.Rows) {
            foreach (var column in dr.ItemArray) {
                if (column == null)
                    sbData.Append(",");
                else
                    sbData.Append("\"" + column.ToString().Replace("\"", "\"\"") + "\",");
            }
            sbData.Replace(",", System.Environment.NewLine, sbData.Length - 1, 1);
        }

        return sbData.ToString();
    }

Anda menyebutnya sebagai berikut:

var csvData = dataTableOject.ToCsv();

1
Yang ini adalah yang terbaik dari yang lainnya di sini. Sudah selesai dilakukan dengan baik. Terima kasih
Fandango68

Solusi yang fantastis. Menambahkan komentar secara lokal, tetapi dapat digunakan di luar kotak tanpa harus mendaki gunung. Terima kasih.
j.hull

Suka ini! Saya menggunakannya sebagai metode non-statis dan baru saja meneruskan DataTable saya sebagai parameter. Bekerja dengan baik, terima kasih.
Kid Koder

9

Jika kode panggilan Anda merujuk ke System.Windows.Formsrakitan, Anda dapat mempertimbangkan pendekatan yang sangat berbeda. Strategi saya adalah menggunakan fungsi yang telah disediakan oleh kerangka kerja untuk mencapai hal ini dalam beberapa baris kode dan tanpa harus mengulang melalui kolom dan baris. Apa yang dilakukan kode di bawah ini adalah secara terprogram membuat a DataGridViewdengan cepat dan menyetel DataGridView.DataSourceke DataTable. Selanjutnya, saya secara terprogram memilih semua sel (termasuk header) di DataGridViewdan memanggil DataGridView.GetClipboardContent(), menempatkan hasilnya ke dalam Windows Clipboard. Kemudian, saya 'menempelkan' konten clipboard ke dalam panggilan ke File.WriteAllText(), memastikan untuk menentukan format 'paste' sebagai TextDataFormat.CommaSeparatedValue.

Ini kodenya:

public static void DataTableToCSV(DataTable Table, string Filename)
{
    using(DataGridView dataGrid = new DataGridView())
    {
        // Save the current state of the clipboard so we can restore it after we are done
        IDataObject objectSave = Clipboard.GetDataObject();

        // Set the DataSource
        dataGrid.DataSource = Table;
        // Choose whether to write header. Use EnableWithoutHeaderText instead to omit header.
        dataGrid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
        // Select all the cells
        dataGrid.SelectAll();
        // Copy (set clipboard)
        Clipboard.SetDataObject(dataGrid.GetClipboardContent());
        // Paste (get the clipboard and serialize it to a file)
        File.WriteAllText(Filename,Clipboard.GetText(TextDataFormat.CommaSeparatedValue));              

        // Restore the current state of the clipboard so the effect is seamless
        if(objectSave != null) // If we try to set the Clipboard to an object that is null, it will throw...
        {
            Clipboard.SetDataObject(objectSave);
        }
    }
}

Perhatikan Saya juga memastikan untuk menyimpan konten clipboard sebelum saya mulai, dan memulihkannya setelah saya selesai, sehingga pengguna tidak mendapatkan banyak sampah tak terduga saat pengguna mencoba menempelkannya. Peringatan utama untuk pendekatan ini adalah 1) Kelas Anda harus referensi System.Windows.Forms, yang mungkin tidak terjadi pada lapisan abstraksi data, 2) Rakitan Anda harus ditargetkan untuk kerangka kerja .NET 4.5, karena DataGridView tidak ada di 4.0, dan 3) Metode ini akan gagal jika papan klip digunakan oleh proses lain.

Bagaimanapun, pendekatan ini mungkin tidak tepat untuk situasi Anda, tetapi tetap menarik, dan dapat menjadi alat lain di kotak peralatan Anda.


1
menggunakan Clipboard tidak diperlukan stackoverflow.com/questions/40726017/… . .GetClipboardContentjuga menangani beberapa kasus tepi nilai yang berisi ,. ", \t(ini mengubah tab menjadi spasi)
Slai

2
Ini bagus, tetapi bagaimana jika seseorang menggunakan mesin pada saat yang sama dan memasukkan sesuatu ke Clipboard pada saat kritis.
Ayo Adesina

7

Saya melakukan ini baru-baru ini tetapi menyertakan tanda kutip ganda di sekitar nilai-nilai saya.

Misalnya, ubah dua baris ini:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 

Terima kasih atas sarannya, tetapi semua datanya masih dalam sel pertama di setiap baris?
Darren Young

7

Coba ganti sb.Append(Environment.NewLine);ke sb.AppendLine();.

StringBuilder sb = new StringBuilder();          
foreach (DataColumn col in dt.Columns)         
{             
    sb.Append(col.ColumnName + ',');         
}          

sb.Remove(sb.Length - 1, 1);         
sb.AppendLine();          

foreach (DataRow row in dt.Rows)         
{             
    for (int i = 0; i < dt.Columns.Count; i++)             
    {                 
        sb.Append(row[i].ToString() + ",");             
    }              

    sb.AppendLine();         
}          

File.WriteAllText("test.csv", sb.ToString());

Itu kemudian akan memberikan dua pengembalian karraige.
Darren Young

@alexl: Itulah yang awalnya saya lakukan, tetapi itu di luar kepala saya sampai VS bersemangat: o)
Neil Knight

5

Cobalah untuk menempatkan, ;bukan,

Semoga membantu


5

Baca ini dan ini ?


Implementasi yang lebih baik adalah

var result = new StringBuilder();
for (int i = 0; i < table.Columns.Count; i++)
{
    result.Append(table.Columns[i].ColumnName);
    result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
}

foreach (DataRow row in table.Rows)
{
    for (int i = 0; i < table.Columns.Count; i++)
    {
        result.Append(row[i].ToString());
        result.Append(i == table.Columns.Count - 1 ? "\n" : ",");
    }
}
 File.WriteAllText("test.csv", result.ToString());

5

Kesalahannya adalah pemisah daftar.

Alih-alih menulis, sb.Append(something... + ',')Anda harus meletakkan sesuatu sepertisb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Anda harus meletakkan karakter pemisah daftar yang dikonfigurasi di sistem operasi Anda (seperti pada contoh di atas), atau pemisah daftar di mesin klien tempat file akan ditonton. Pilihan lain adalah mengonfigurasinya di app.config atau web.config sebagai parameter aplikasi Anda.


5

4 baris kode:

public static string ToCSV(DataTable tbl)
{
    StringBuilder strb = new StringBuilder();

    //column headers
    strb.AppendLine(string.Join(",", tbl.Columns.Cast<DataColumn>()
        .Select(s => "\"" + s.ColumnName + "\"")));

    //rows
    tbl.AsEnumerable().Select(s => strb.AppendLine(
        string.Join(",", s.ItemArray.Select(
            i => "\"" + i.ToString() + "\"")))).ToList();

    return strb.ToString();
}

Perhatikan bahwa ToList()di akhir itu penting; Saya butuh sesuatu untuk memaksa evaluasi ekspresi. Jika saya bermain golf kode, saya bisa menggunakannya Min().

Perhatikan juga bahwa hasilnya akan memiliki baris baru di akhir karena panggilan terakhir ke AppendLine(). Anda mungkin tidak menginginkan ini. Anda cukup menelepon TrimEnd()untuk menghapusnya.


3

Berikut adalah peningkatan pada posting vc-74 yang menangani koma dengan cara yang sama seperti Excel. Excel memberi tanda kutip di sekitar data jika data memiliki koma tetapi tidak mengutip jika data tidak memiliki koma.

    public static string ToCsv(this DataTable inDataTable, bool inIncludeHeaders = true)
    {
        var builder = new StringBuilder();
        var columnNames = inDataTable.Columns.Cast<DataColumn>().Select(column => column.ColumnName);
        if (inIncludeHeaders)
            builder.AppendLine(string.Join(",", columnNames));
        foreach (DataRow row in inDataTable.Rows)
        {
            var fields = row.ItemArray.Select(field => field.ToString().WrapInQuotesIfContains(","));
            builder.AppendLine(string.Join(",", fields));
        }

        return builder.ToString();
    }

    public static string WrapInQuotesIfContains(this string inString, string inSearchString)
    {
        if (inString.Contains(inSearchString))
            return "\"" + inString+ "\"";
        return inString;
    }

2

Untuk menulis ke file, menurut saya metode berikut adalah yang paling efisien dan mudah: (Anda dapat menambahkan tanda kutip jika mau)

public static void WriteCsv(DataTable dt, string path)
{
    using (var writer = new StreamWriter(path)) {
        writer.WriteLine(string.Join(",", dt.Columns.Cast<DataColumn>().Select(dc => dc.ColumnName)));
        foreach (DataRow row in dt.Rows) {
            writer.WriteLine(string.Join(",", row.ItemArray));
        }
    }
}

2

Untuk meniru CSV Excel:

public static string Convert(DataTable dt)
{
    StringBuilder sb = new StringBuilder();

    IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                        Select(column => column.ColumnName);
    sb.AppendLine(string.Join(",", columnNames));

    foreach (DataRow row in dt.Rows)
    {
        IEnumerable<string> fields = row.ItemArray.Select(field =>
        {
            string s = field.ToString().Replace("\"", "\"\"");
            if(s.Contains(','))
                s = string.Concat("\"", s, "\"");
            return s;
        });
        sb.AppendLine(string.Join(",", fields));
    }

    return sb.ToString().Trim();
}

1
StringBuilder sb = new StringBuilder();
        SaveFileDialog fileSave = new SaveFileDialog();
        IEnumerable<string> columnNames = tbCifSil.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(",", columnNames));

        foreach (DataRow row in tbCifSil.Rows)
        {
            IEnumerable<string> fields = row.ItemArray.Select(field =>string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
            sb.AppendLine(string.Join(",", fields));
        }

        fileSave.ShowDialog();
        File.WriteAllText(fileSave.FileName, sb.ToString());

Selamat datang di StackOverflow! Jawaban paling baik jika menyertakan deskripsi cuplikan kode. Saya pribadi menemukan bahwa ketika nama variabel berbaris di antara pertanyaan dan jawaban, mereka lebih membantu saya.
AWinkle

1
public void ExpoetToCSV(DataTable dtDataTable, string strFilePath)
{

    StreamWriter sw = new StreamWriter(strFilePath, false);
    //headers   
    for (int i = 0; i < dtDataTable.Columns.Count; i++)
    {
        sw.Write(dtDataTable.Columns[i].ToString().Trim());
        if (i < dtDataTable.Columns.Count - 1)
        {
            sw.Write(",");
        }
    }
    sw.Write(sw.NewLine);
    foreach (DataRow dr in dtDataTable.Rows)
    {
        for (int i = 0; i < dtDataTable.Columns.Count; i++)
        {
            if (!Convert.IsDBNull(dr[i]))
            {
                string value = dr[i].ToString().Trim();
                if (value.Contains(','))
                {
                    value = String.Format("\"{0}\"", value);
                    sw.Write(value);
                }
                else
                {
                    sw.Write(dr[i].ToString().Trim());
                }
            }
            if (i < dtDataTable.Columns.Count - 1)
            {
                sw.Write(",");
            }
        }
        sw.Write(sw.NewLine);
    }
    sw.Close();
}

1

Mungkin, cara paling mudah adalah menggunakan:

https://github.com/ukushu/DataExporter

terutama jika data Anda berisi /r/nkarakter atau simbol pemisah yang berisi karakter atau simbol pemisah di dalam sel dataTable Anda. Hampir semua jawaban lain tidak akan berfungsi dengan sel seperti itu.

Anda hanya perlu menulis kode berikut:

Csv csv = new Csv("\t");//Needed delimiter 

var columnNames = dt.Columns.Cast<DataColumn>().
    Select(column => column.ColumnName).ToArray();

csv.AddRow(columnNames);

foreach (DataRow row in dt.Rows)
{
    var fields = row.ItemArray.Select(field => field.ToString()).ToArray;
    csv.AddRow(fields);   
}

csv.Save();

0

Jika ada orang lain yang tersandung dalam hal ini, saya menggunakan File.ReadAllText untuk mendapatkan data CSV dan kemudian saya memodifikasinya dan menulisnya kembali dengan File.WriteAllText . \ R \ n CRLF baik-baik saja tetapi \ t tab diabaikan saat Excel membukanya. (Semua solusi di utas ini sejauh ini menggunakan pemisah koma tetapi itu tidak masalah.) Notepad menunjukkan format yang sama di file yang dihasilkan seperti di sumbernya. A Diff bahkan menunjukkan file-file itu identik. Tapi saya mendapat petunjuk ketika saya membuka file di Visual Studio dengan editor biner. File sumbernya adalah Unicode tetapi targetnya adalah ASCII . Untuk memperbaikinya, saya memodifikasi ReadAllText dan WriteAllText dengan argumen ketiga ditetapkan sebagai System.Text.Encoding.Unicode , dan dari sana Excel dapat membuka file yang diperbarui.


0

FYR

private string ExportDatatableToCSV(DataTable dtTable)
{
    StringBuilder sbldr = new StringBuilder();
    if (dtTable.Columns.Count != 0)
    {
        foreach (DataColumn col in dtTable.Columns)
        {
            sbldr.Append(col.ColumnName + ',');
        }
        sbldr.Append("\r\n");
        foreach (DataRow row in dtTable.Rows)
        {
            foreach (DataColumn column in dtTable.Columns)
            {
                sbldr.Append(row[column].ToString() + ',');
            }
            sbldr.Append("\r\n");
        }
    }
    return sbldr.ToString();
}

0

Inilah solusi saya, berdasarkan jawaban sebelumnya oleh Paul Grimshaw dan Anthony VO . Saya telah mengirimkan kode dalam proyek C # di Github .

Kontribusi utama saya adalah menghilangkan secara eksplisit pembuatan dan manipulasi StringBuilderdan alih-alih hanya bekerja dengan IEnumerable. Ini menghindari alokasi buffer yang besar di memori.

public static class Util
{
    public static string EscapeQuotes(this string self) {
        return self?.Replace("\"", "\"\"") ?? "";
    }

    public static string Surround(this string self, string before, string after) {
        return $"{before}{self}{after}";
    }

    public static string Quoted(this string self, string quotes = "\"") {
        return self.Surround(quotes, quotes);
    }

    public static string QuotedCSVFieldIfNecessary(this string self) {
        return (self == null) ? "" : self.Contains('"') ? self.Quoted() : self; 
    }

    public static string ToCsvField(this string self) {
        return self.EscapeQuotes().QuotedCSVFieldIfNecessary();
    }

    public static string ToCsvRow(this IEnumerable<string> self){
        return string.Join(",", self.Select(ToCsvField));
    }

    public static IEnumerable<string> ToCsvRows(this DataTable self) {          
        yield return self.Columns.OfType<object>().Select(c => c.ToString()).ToCsvRow();
        foreach (var dr in self.Rows.OfType<DataRow>())
            yield return dr.ItemArray.Select(item => item.ToString()).ToCsvRow();
    }

    public static void ToCsvFile(this DataTable self, string path) {
        File.WriteAllLines(path, self.ToCsvRows());
    }

}

Pendekatan ini digabungkan dengan baik dengan mengonversi IEnumerableke DataTable seperti yang ditanyakan di sini .


0
        DataTable dt = yourData();
        StringBuilder csv = new StringBuilder();
        int dcCounter = 0;

        foreach (DataColumn dc in dt.Columns)
        {
            csv.Append(dc);
            if (dcCounter != dt.Columns.Count - 1)
            {
                csv.Append(",");
            }
            dcCounter++;
        }
        csv.AppendLine();

        int numOfDc = dt.Columns.Count;
        foreach (DataRow dr in dt.Rows)
        {
            int colIndex = 0;
            while (colIndex <= numOfDc - 1)
            {
                var colVal = dr[colIndex].ToString();
                if (colVal != null && colVal != "")
                {
                    DateTime isDateTime;
                    if (DateTime.TryParse(colVal, out isDateTime))
                    {
                        csv.Append(Convert.ToDateTime(colVal).ToShortDateString());
                    }
                    else
                    {
                        csv.Append(dr[colIndex]);
                    }
                }
                else
                {
                    csv.Append("N/A");
                }
                if (colIndex != numOfDc - 1)
                {
                    csv.Append(",");
                }
                colIndex++;
            }
            csv.AppendLine();

Saya juga perlu melakukan beberapa overriding data, itulah sebabnya ada beberapa pernyataan "jika lagi". Saya perlu memastikan bahwa jika bidang kosong untuk memasukkan "T / A" sebagai gantinya, atau jika bidang Tanggal diformat sebagai "01/01/1900: 00" maka itu akan disimpan sebagai "01/01/1900" sebagai gantinya.


0
StringBuilder sb = new StringBuilder();

        foreach (DataColumn col in table.Columns)
        {
            sb.Append(col.ColumnName + ";");
        }

        foreach (DataRow row in table.Rows)
        {
            sb.AppendLine();
            foreach (DataColumn col in table.Columns)
            {
                sb.Append($@"{Convert.ToString(row[col])}" + ";");
            }
        }
        File.WriteAllText(path, sb.ToString());

-1

Jika semua data masih di sel pertama, berarti aplikasi yang Anda buka file tersebut mengharapkan pembatas lain. MSExcel dapat menangani koma sebagai pembatas kecuali Anda menentukan sebaliknya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.