c # с возможностью передачи данных в csv - PullRequest
98 голосов
/ 10 февраля 2011

Может кто-нибудь сказать мне, почему следующий код не работает. Данные сохраняются в файл CSV, однако данные не разделяются. Все это существует в первой ячейке каждой строки.

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

Спасибо.

Ответы [ 21 ]

204 голосов
/ 10 февраля 2011

Следующая короткая версия прекрасно открывается в Excel, возможно, ваша проблема заключалась в запятой

.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

И, как указал Тим, если вы используете .net> = 4, вы можете сделать его еще короче:

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

Как предполагает Кристиан, если вы хотите обрабатывать специальные символы, экранирующие поля, замените блок цикла на:

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

И последнее предложение: вы можете писать содержимое строки csv построчно, а не как целый документ, чтобы избежать большого документа в памяти.

34 голосов
/ 19 марта 2012

Я обернул это в класс расширения, который позволяет вам звонить:

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

в любом DataTable.

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());
    }
}
20 голосов
/ 09 августа 2012

Новая функция расширения, основанная на ответе Пола Гримшоу. Я убрал его и добавил возможность обрабатывать неожиданные данные. (Пустые данные, встроенные кавычки и запятые в заголовках ...)

Он также возвращает строку, которая является более гибкой. Он возвращает Null, если объект таблицы не содержит никакой структуры.

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

Вы называете это следующим образом:

var csvData = dataTableOject.ToCsv();
8 голосов
/ 08 октября 2014

Если ваш код вызова ссылается на сборку System.Windows.Forms, вы можете рассмотреть принципиально иной подход. Моя стратегия состоит в том, чтобы использовать функции, уже предоставленные платформой, для достижения этой цели в очень небольшом количестве строк кода без необходимости циклически перебирать столбцы и строки. Код, приведенный ниже, программно создает DataGridView на лету и устанавливает DataGridView.DataSource на DataTable. Затем я программно выбираю все ячейки (включая заголовок) в DataGridView и вызываю DataGridView.GetClipboardContent(), помещая результаты в Windows Clipboard. Затем я «вставляю» содержимое буфера обмена в вызов File.WriteAllText(), убедившись, что форматирование «вставки» указано как TextDataFormat.CommaSeparatedValue.

Вот код:

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

Обратите внимание, что я также должен сохранить содержимое буфера обмена до того, как я начну, и восстановить его, как только я закончу, чтобы пользователь не получил кучу неожиданного мусора в следующий раз, когда пользователь попытается вставить. Основные предостережения в отношении этого подхода: 1) Ваш класс должен ссылаться на System.Windows.Forms, что может быть не так на уровне абстракции данных, 2) Ваша сборка должна быть ориентирована на платформу .NET 4.5, поскольку DataGridView не существует в 4.0 и 3) метод не будет работать, если буфер обмена используется другим процессом.

В любом случае, этот подход может не подходить для вашей ситуации, но, тем не менее, он интересен и может стать еще одним инструментом в вашем наборе инструментов.

6 голосов
/ 10 февраля 2011

Я сделал это недавно, но включил двойные кавычки вокруг своих значений.

Например, измените эти две строки:

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 
6 голосов
/ 10 февраля 2011

Попробуйте изменить sb.Append(Environment.NewLine); на 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());
5 голосов
/ 07 августа 2012

Ошибка - разделитель списка.

Вместо записи sb.Append(something... + ',') вы должны поместить что-то вроде sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);

Вы должны поместить символ разделителя списка, настроенный в вашей операционной системе (напримерв приведенном выше примере), или разделитель списка на клиентском компьютере, где будет просматриваться файл.Другой вариант - настроить его в app.config или web.config в качестве параметра вашего приложения.

5 голосов
/ 10 февраля 2011

Читать это и это ?Лучшая реализация будет

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 голосов
/ 10 февраля 2011

Попробуйте поставить ; вместо ,

Надеюсь, это поможет

4 голосов
/ 12 августа 2014

4 строки кода:

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

Обратите внимание, что ToList() в конце важен; Мне нужно что-то, чтобы заставить выражение выражения. Если бы я занимался кодовым гольфом, я мог бы использовать Min().

Также обратите внимание, что в результате в конце появится символ новой строки из-за последнего вызова AppendLine(). Вы можете не хотеть этого. Вы можете просто позвонить TrimEnd(), чтобы удалить его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...