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, если объект таблицы не содержит никакой структуры.
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);
tosb.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 с третьим аргументом, установленным как
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(); }