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 ответов


следующая более короткая версия открывается отлично в 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, если объект table не содержит никакой структуры.

    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(), размещение результатов в 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 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.config как parammeter вашего приложения.


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. \R \ n CRLFs были в порядке, но вкладки \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 может обрабатывать запятую как разделитель, если не указано иное.