c # datatable sang csv


113

Ai đó có thể vui lòng cho tôi biết tại sao mã sau đây không hoạt động. Dữ liệu được lưu vào tệp csv, tuy nhiên dữ liệu không được tách biệt. Tất cả đều tồn tại trong ô đầu tiên của mỗi hàng.

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

Cảm ơn.



Tôi đã phát triển Extention hiệu suất cao. kiểm tra câu trả lời này
Nigje

Câu trả lời:


229

Phiên bản ngắn hơn sau đây mở tốt trong Excel, có thể vấn đề của bạn là dấu phẩy ở cuối

.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

Và như Tim đã chỉ ra, nếu bạn đang sử dụng .net> = 4, bạn có thể làm cho nó ngắn hơn nữa:

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

Theo đề xuất của Christian, nếu bạn muốn xử lý các ký tự đặc biệt thoát ra trong các trường, hãy thay thế khối vòng lặp bằng cách:

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

Và gợi ý cuối cùng, bạn có thể viết nội dung csv từng dòng thay vì toàn bộ tài liệu, để tránh có một tài liệu lớn trong bộ nhớ.


2
Không cần sao chép ItemArraysang một tệp mới String[], bạn có thể bỏ qua .ToArray()với .NET 4 và sử dụng String.Joinquá tảiIEnumerable<T>(đã chỉnh sửa).
Tim Schmelter

2
@TimSchmelter, vâng nhưng những quá tải đã được giới thiệu trong .net4, mã sẽ không biên dịch nếu OP sử dụng .net <4
vc 74

18
Phương thức này không tính đến dấu phẩy bên trong giá trị cột.
Christian

2
Thay vào đó IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", các lĩnh vực) + "\" ");
Christian

2
@ Si8 Ý bạn là gì? Câu trả lời này chỉ sử dụng các thành phần db và &nbsplà điển hình của các tài liệu HTML / XML. Nó không phải là mã ở trên tạo ra nó trừ khi bảng chứa &nbsp;một cách rõ ràng
vc 74

36

Tôi đã gói nó thành một lớp mở rộng, cho phép bạn gọi:

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

trên bất kỳ DataTable nào.

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

Một chức năng mở rộng mới dựa trên câu trả lời của Paul Grimshaw. Tôi đã dọn dẹp nó và thêm khả năng xử lý dữ liệu không mong muốn. (Dữ liệu trống, Trích dẫn được nhúng và dấu phẩy trong tiêu đề ...)

Nó cũng trả về một chuỗi linh hoạt hơn. Nó trả về Null nếu đối tượng bảng không chứa bất kỳ cấu trúc nào.

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

Bạn gọi nó như sau:

var csvData = dataTableOject.ToCsv();

1
Cái này là tốt nhất trong số phần còn lại ở đây. Làm tốt. Cảm ơn
Fandango68

Giải pháp tuyệt vời. Đã thêm nhận xét tại địa phương, nhưng có thể sử dụng ra khỏi hộp mà không cần phải leo núi. Cảm ơn bạn.
j.hull

Tình yêu này! Tôi đã sử dụng nó như một phương thức không tĩnh và chỉ chuyển DataTable của tôi làm tham số. Làm việc rất tốt, cảm ơn bạn.
Kid Koder

9

Nếu mã gọi của bạn đang tham chiếu đến System.Windows.Formsassembly, bạn có thể xem xét một cách tiếp cận hoàn toàn khác. Chiến lược của tôi là sử dụng các chức năng đã được cung cấp bởi khung công tác để thực hiện điều này trong rất ít dòng mã và không cần phải lặp qua các cột và hàng. Có gì đoạn code dưới đây là lập trình tạo ra một DataGridViewmột cách nhanh chóng và thiết lập DataGridView.DataSourceđến DataTable. Tiếp theo, tôi chọn lập trình tất cả các ô (bao gồm cả tiêu đề) trong lệnh DataGridViewgọi và DataGridView.GetClipboardContent(), đặt kết quả vào Windows Clipboard. Sau đó, tôi 'dán' nội dung của khay nhớ tạm vào lệnh gọi tới File.WriteAllText(), đảm bảo chỉ định định dạng của 'dán' là TextDataFormat.CommaSeparatedValue.

Đây là mã:

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

Lưu ý rằng tôi cũng đảm bảo giữ nguyên nội dung của khay nhớ tạm trước khi bắt đầu và khôi phục nó sau khi tôi hoàn tất, để người dùng không nhận được một đống rác không mong muốn vào lần tiếp theo người dùng cố gắng dán. Những lưu ý chính đối với cách tiếp cận này là 1) Lớp của bạn phải tham chiếu System.Windows.Forms, điều này có thể không đúng với lớp trừu tượng hóa dữ liệu, 2) Hợp ngữ của bạn sẽ phải được nhắm mục tiêu cho khung .NET 4.5, vì DataGridView không tồn tại trong 4.0, và 3) Phương pháp này sẽ không thành công nếu khay nhớ tạm được sử dụng bởi một quy trình khác.

Dù sao, cách tiếp cận này có thể không phù hợp với tình huống của bạn, nhưng nó không kém phần thú vị và có thể là một công cụ khác trong hộp công cụ của bạn.


1
sử dụng Bảng tạm là không cần thiết stackoverflow.com/questions/40726017/… . .GetClipboardContentcũng xử lý một số trường hợp cạnh của các giá trị có chứa ,. ", \t(nó chuyển đổi tab thành không gian)
Slai

2
Điều này là tốt, nhưng điều gì sẽ xảy ra nếu ai đó đang sử dụng máy cùng lúc và đưa thứ gì đó vào Clipboard vào thời điểm quan trọng.
Ayo Adesina

7

Tôi đã làm điều này gần đây nhưng bao gồm dấu ngoặc kép xung quanh các giá trị của tôi.

Ví dụ: thay đổi hai dòng sau:

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

Cảm ơn bạn đã gợi ý, nhưng tất cả dữ liệu vẫn nằm trong ô đầu tiên của mỗi hàng?
Darren Young

7

Hãy thử thay đổi sb.Append(Environment.NewLine);thành 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());

Điều đó sau đó sẽ trả lại hai carraige.
Darren Young

@alexl: Đó là những gì tôi đã được ban đầu được đi với, nhưng nó đã được ra khỏi đỉnh đầu của tôi cho đến khi VS bắn lên: o)
Neil Hiệp sĩ

5

Cố gắng đặt ;thay vì,

Hy vọng nó giúp


5

Đọc cái nàycái này ?


Việc triển khai tốt hơn sẽ là

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

Lỗi là dấu phân tách danh sách.

Thay vì viết, sb.Append(something... + ',')bạn nên đặt một cái gì đó nhưsb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Bạn phải đặt ký tự phân tách danh sách được định cấu hình trong hệ điều hành của bạn (như trong ví dụ trên) hoặc ký tự phân tách danh sách trong máy khách nơi tệp sẽ được xem. Một tùy chọn khác là định cấu hình nó trong app.config hoặc web.config dưới dạng tham số của ứng dụng của bạn.


5

4 dòng mã:

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

Lưu ý rằng phần ToList()cuối là quan trọng; Tôi cần một cái gì đó để buộc đánh giá biểu thức. Nếu tôi đang chơi gôn mã, tôi có thể sử dụng Min()thay thế.

Cũng lưu ý rằng kết quả sẽ có một dòng mới ở cuối vì cuộc gọi cuối cùng đến AppendLine(). Bạn có thể không muốn điều này. Bạn có thể chỉ cần gọi TrimEnd()để loại bỏ nó.


3

Đây là một cải tiến cho bài đăng của vc-74 xử lý dấu phẩy giống như cách Excel làm. Excel đặt dấu ngoặc kép xung quanh dữ liệu nếu dữ liệu có dấu phẩy nhưng không trích dẫn nếu dữ liệu không có dấu phẩy.

    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

Để ghi vào một tệp, tôi nghĩ rằng phương pháp sau là hiệu quả và đơn giản nhất: (Bạn có thể thêm dấu ngoặc kép nếu muốn)

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

Để bắt chước Excel CSV:

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

Chào mừng bạn đến với StackOverflow! Câu trả lời tốt nhất khi chúng bao gồm mô tả của đoạn mã. Cá nhân tôi thấy rằng khi các tên biến xếp hàng giữa câu hỏi và câu trả lời, chúng hữu ích hơn cho tôi.
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

Có thể, cách dễ dàng nhất sẽ là sử dụng:

https://github.com/ukushu/DataExporter

đặc biệt là trong trường hợp dữ liệu của bạn có thể dữ liệu chứa các /r/nký tự hoặc ký hiệu dấu phân cách bên trong các ô dataTable của bạn. Hầu như tất cả các câu trả lời khác sẽ không hoạt động với các ô như vậy.

bạn chỉ cần viết đoạn mã sau:

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

Trong trường hợp bất kỳ ai khác tình cờ gặp phải điều này, tôi đã sử dụng File.ReadAllText để lấy dữ liệu CSV, sau đó tôi sửa đổi nó và viết lại bằng File.WriteAllText . \ R \ n CRLF vẫn ổn nhưng các tab \ t bị bỏ qua khi Excel mở nó. (Tất cả các giải pháp trong chủ đề này cho đến nay đều sử dụng dấu phân cách bằng dấu phẩy nhưng điều đó không quan trọng.) Notepad đã hiển thị cùng một định dạng trong tệp kết quả như trong nguồn. Một Diff thậm chí còn cho thấy các tệp giống hệt nhau. Nhưng tôi có manh mối khi tôi mở tệp trong Visual Studio bằng trình chỉnh sửa nhị phân. Tệp nguồn là Unicode nhưng đích là ASCII . Để khắc phục, tôi đã sửa đổi cả ReadAllText và WriteAllText với đối số thứ ba được đặt là System.Text.Encoding.Unicode và từ đó Excel có thể mở tệp cập nhật.


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

Đây là giải pháp của tôi, dựa trên các câu trả lời trước đây của Paul GrimshawAnthony VO . Tôi đã gửi mã trong một dự án C # trên Github .

Đóng góp chính của tôi là loại bỏ việc tạo và thao tác một cách rõ ràng StringBuildervà thay vào đó chỉ làm việc với IEnumerable. Điều này tránh việc cấp phát một bộ đệm lớn trong bộ nhớ.

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

}

Cách tiếp cận này kết hợp độc đáo với việc chuyển đổi IEnumerablesang DataTable như được hỏi ở đây .


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

Tôi cũng cần thực hiện một số ghi đè dữ liệu, đó là lý do tại sao có một vài câu lệnh "nếu khác". Tôi cần đảm bảo rằng nếu một trường trống để nhập "N / A" hoặc nếu trường Ngày được định dạng là "01/01/1900: 00" thì trường đó sẽ được lưu là "01/01/1900" thay thế.


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

nếu tất cả dữ liệu vẫn còn trong ô đầu tiên, điều đó có nghĩa là ứng dụng bạn đã mở tệp bằng đang mong đợi một dấu phân cách khác. MSExcel có thể xử lý dấu phẩy làm dấu phân cách trừ khi bạn chỉ định khác.

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.