c # данные в CSV

113

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

Спасибо.

Даррен Янг
источник
Вы можете проверить это gist.github.com/riyadparvez/4467668
пользователь
Я разработал высокопроизводительное расширение. проверьте этот ответ
Nigje

Ответы:

229

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

vc 74
источник
2
Нет необходимости копировать в ItemArrayновый String[], вы можете опустить .ToArray()с .NET 4 и использовать String.Joinперегрузку, которая принимает IEnumerable<T>(отредактировано).
Тим Шмелтер
2
@TimSchmelter, да, но эти перегрузки были введены в .net4, код не будет компилироваться, если OP использует .net <4
vc 74
18
Этот метод не учитывает запятую внутри значения столбца.
Кристиан
2
Вместо этого IEnumerable <string> fields = row.ItemArray.Select (field => field.ToString (). Replace ("\" "," \ "\" ")); sb.AppendLine (" \ "" + string.Join ("\", \ "", fields) + "\" ");
Christian
2
@ Si8 Что ты имеешь в виду? Этот ответ использует только компоненты db и &nbspтипичен для документов HTML / XML. Это не приведенный выше код, который производит его, если таблица не содержит &nbsp;явно
vc 74
37

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

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());
    }
}
Пол Гримшоу
источник
25

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

Он также возвращает более гибкую строку. Он возвращает 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();
AnthonyVO
источник
1
Это лучший из остальных здесь. Отлично сработано. Спасибо
Fandango68
Фантастическое решение. Добавил комментарии локально, но смог использовать из коробки без необходимости подниматься на гору. Спасибо.
j.hull
Очень понравилось! Я использовал его как нестатический метод и просто передал свой DataTable в качестве параметра. Отлично поработали, спасибо.
Кид Кодер,
9

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

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

Адам Уайт
источник
1
использование буфера обмена не требуется stackoverflow.com/questions/40726017/… . .GetClipboardContentтакже обрабатывает несколько крайних случаев значений, содержащих ,. ", \t(конвертирует табуляцию в пробел)
Slai
2
Это хорошо, но что, если кто-то одновременно использует машину и помещает что-то в буфер обмена в критический момент.
Айо Адесина
7

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

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

sb.Append("\"" + col.ColumnName + "\","); 
...
sb.Append("\"" + row[i].ToString() + "\","); 
Бен Якубен
источник
Спасибо за предложение, но все ли данные по-прежнему находятся в первой ячейке каждой строки?
Даррен Янг
7

Попробуйте сменить 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());
Нил Найт
источник
Это даст два возврата каррейджа.
Даррен Янг
@alexl: Это то, к чему я изначально стремился, но это было не в моей голове, пока VS не загорелся: o)
Нил Найт
5

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

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

Alexl
источник
5

Прочитать это и это ?


Лучшая реализация была бы

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

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

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

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

Мартин Делафуэнте
источник
5

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

user2023861
источник
3

Вот усовершенствование сообщения 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;
    }
Rhyous
источник
2

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

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));
        }
    }
}
Студент222
источник
2

Чтобы имитировать 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());
Нам Нгуен
источник
Добро пожаловать в StackOverflow! Лучше всего отвечать, когда они включают описание фрагмента кода. Я лично обнаружил, что когда имена переменных выстраиваются между вопросом и ответом, они более полезны для меня.
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();
}
Ghebrehiywet
источник
1

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

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();
Андрей
источник
0

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

TonyG
источник
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

Вот мое решение, основанное на предыдущих ответах по Paul Гримшоу и Энтони VO . Я отправил код в проект C # на Github .

Мой основной вклад - исключить явное создание и манипулирование a StringBuilderи вместо этого работать только с IEnumerable. Это позволяет избежать выделения большого буфера в памяти.

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

}

Этот подход прекрасно сочетается с преобразованием IEnumerableв DataTable, как здесь указано .

cdiggins
источник
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();

Мне также нужно было переопределить данные, поэтому есть несколько операторов «если еще». Мне нужно было убедиться, что если поле было пустым, чтобы ввести вместо него «N / A», или если поле Date было отформатировано как «01.01.1900: 00», оно будет сохранено как «01.01.1900» вместо.

BondAddict
источник
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

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

Акрам Агбарья
источник