c# datatable в csv


может кто-то пожалуйста, скажите мне, почему следующий код не работает. Данные сохраняются в 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());

спасибо.

19 87

19 ответов:

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

.чистая = 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());

.нетто >= 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 строка за строкой вместо того, чтобы в целом документ, чтобы избежать наличия большого документа в памяти.

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

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

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

Она также возвращает строку, которая является более гибкой. Он возвращает 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();

если ваш вызывающий код ссылается на System.Windows.Forms сборка, вы можете рассмотреть принципиально иной подход. Моя стратегия состоит в том, чтобы использовать функции, уже предоставленные фреймворком, чтобы выполнить это в очень немногих строках кода и без необходимости перебирать столбцы и строки. То, что код ниже делает это программно создать DataGridView на лету и поставил DataGridView.DataSource до DataTable. Затем я программно выбираю все ячейки (включая заголовок) в DataGridView и звонок DataGridView.GetClipboardContent(), размещение результатов в окнах 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 framework, поскольку DataGridView не существует в 4.0, и 3) Метод завершится неудачей, если буфер обмена используется другим процессом.

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

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

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

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

попробуйте изменить sb.Append(Environment.NewLine); to 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());

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

надеюсь, что это помогает

читать этой и этой?


Лучшая реализация была бы
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());

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

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

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

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(), чтобы удалить его.

вот усовершенствование сообщения vc-74, которое обрабатывает запятые так же, как это делает Excel. Excel помещает кавычки вокруг данных, если данные имеют запятую, но не цитирует, если данные не имеют запятую.

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

писать в файл, я думаю, что следующий метод является наиболее эффективным и простым: (Вы можете добавить кавычки, если вы хотите)

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

возможно, самый простой способ будет использовать:

https://github.com/ukushu/DataExporter

особенно в случае ваших данных datatable, содержащих /r/n символы или символ разделителя внутри ячеек dataTable.

только вам нужно написать следующий код:

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

для имитации 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();
}

в случае, если кто-то еще наткнется на это, я использовал .ReadAllText чтобы получить данные CSV, а затем я изменил его и написал его обратно с .WriteAllText. CRLFs \r\n были прекрасны, но вкладки \t были проигнорированы, когда Excel открыл его. (Все решения в этом потоке до сих пор используют разделитель запятой, но это не имеет значения.) Блокнот показал тот же формат в результирующем файле, что и в исходном. Разница даже показала, что файлы идентичны. Но я получил ключ, когда я открыл файл в Visual Studio с двоичным редактором. Исходный файл был Unicode, но цель была ASCII. Чтобы исправить, я изменил как ReadAllText, так и WriteAllText с третьим аргументом, установленным как

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

Если все данные по-прежнему находятся в первой ячейке, это означает, что приложение, с которым вы открыли файл, ожидает другого разделителя. MSExcel может обрабатывать запятую как разделитель, если не указано иное.