Converting DataSet/DataTable to CSV - c#

Please let me know, if there any way to generate CSV files from a DataTable or DataSet? To be specific, without manually iterating through rows of DataTable and concatenating.
Please help

There are several ways to do that.
One of the simplest (IMO) is using FileHelpers Library
FileHelpers.CsvEngine.DataTableToCsv(dataTable, filename);

A relative simple, compact and quite flexible solution could be the following extension method:
public static string ToCsv(this DataTable table, string colSep = "", string rowSep = "\r\n")
{
var format = string.Join(colSep, Enumerable.Range(0, table.Columns.Count)
.Select(i => string.Format("{{{0}}}", i)));
return string.Join(rowSep, table.Rows.OfType<DataRow>()
.Select(i => string.Format(format, i.ItemArray)));
}
Please note that this solution could cause problems with huge amounts of data, in which case you should stream the output. Quoting and formatting would of course make the code more complex.

There is, I hope, also a possible way for doing that:
static void Main(string[] args)
{
DataTable dt = new DataTable("MyTable");
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Name", typeof(string));
DataRow dr1 = dt.NewRow();
dr1["Id"] = 1;
dr1["Name"] = "John Smith";
dt.Rows.Add(dr1);
DataRow dr2 = dt.NewRow();
dr2["Id"] = 2;
dr2["Name"] = "John West";
dt.Rows.Add(dr2);
List<DataRow> list = dt.AsEnumerable().ToList();
var strlist = from dr in list
select dr[0] + ", " + dr[1];
var csv = string.Join(Environment.NewLine,strlist);
Console.WriteLine(csv);
}

//Dataset To Xls
ExportDataSetToCsvFile(DS,#"C:\\");
internal static void ExportDataSetToCsvFile(DataSet _DataSet, string DestinationCsvDirectory)
{
try
{
foreach (DataTable DDT in _DataSet.Tables)
{
String MyFile = #DestinationCsvDirectory + "\\_" + DDT.TableName.ToString() + DateTime.Now.ToString("yyyyMMddhhMMssffff") + ".csv";//+ DateTime.Now.ToString("ddMMyyyyhhMMssffff")
using (var outputFile = File.CreateText(MyFile))
{
String CsvText = string.Empty;
foreach (DataColumn DC in DDT.Columns)
{
if (CsvText != "")
CsvText = CsvText + "," + DC.ColumnName.ToString();
else
CsvText = DC.ColumnName.ToString();
}
outputFile.WriteLine(CsvText.ToString().TrimEnd(','));
CsvText = string.Empty;
foreach (DataRow DDR in DDT.Rows)
{
foreach (DataColumn DCC in DDT.Columns)
{
if (CsvText != "")
CsvText = CsvText + "," + DDR[DCC.ColumnName.ToString()].ToString();
else
CsvText = DDR[DCC.ColumnName.ToString()].ToString();
}
outputFile.WriteLine(CsvText.ToString().TrimEnd(','));
CsvText = string.Empty;
}
System.Threading.Thread.Sleep(1000);
}
}
}
catch (Exception Ex)
{
throw Ex;
}
}

So this is a fairly bizarre solution, but it works faster than most as it makes use of the JSON.net library's serialization. This speeds the solution up significantly.
Steps:
Create array of every column name in the data table, should be
simple
Use JSON.net to convert datatable to a json string
string json = JsonConvert.SerializeObject(dt, Formatting.None);
Begin making use of the Replace function on c# strings and strip the
json string of all json formatting.
json = json.Replace("\"", "").Replace("},{", "\n").Replace(":", "").Replace("[{", "").Replace("}]", "");
Then use the array from step 1 to remove all column names from the
json string. You are left with a csv formatted string.
Consider using the array created in step 1 to add the column names
back in as the first row in csv format.

Related

C# get string out of DataTable and send it via tcp

im trying to get an string out of a PostgreSQL database and send it via TCP to another client.
string dataTable = Database.DtConvertToString(Database.SelectData("SELECT username FROM accounts WHERE user_id = 3"));
should give me the actual username but it just gives me the name of the column "username"
The method :
public static string DtConvertToString(DataTable dt)
{
string rw = "";
StringBuilder builder = new StringBuilder();
foreach (DataRow dr in dt.Rows)
{
foreach (DataColumn dc in dr.Table.Columns)
{
rw = dc.ToString();
if (rw.Contains(",")) rw = "\"" + rw + "\"";
builder.Append(rw + ",");
}
builder.Append(Environment.NewLine);
}
return builder.ToString();
}
Can you tell me what's wrong with my thinking?
You're making this way too complicated:
string dataTable = Database.SelectData("SELECT username FROM accounts WHERE user_id = 3").Rows[0]["username"].ToString();
If you really want to preserve the DtConvertToString() method, it should look more like this:
public static string DtConvertToString(DataTable dt, bool showHeaders = true)
{
StringBuilder result = new StringBuilder();
if (showHeaders)
{
var delimiter = "";
foreach(var c in dt.Columns)
{
var colName = c.ColumnName;
if (colName.Contains(",")) colName = $"\"{colName}\"";
result.Append(delimiter).Append(colName);
delimiter = ",";
}
result.AppendLine();
}
for (int r = 0;i<dt.Rows.Count;r++)
{
var row = dt.Rows[r].ItemArray;
var delimiter = "";
foreach(var col in row)
{
if (col.Contains(",")) col = $"\"{col}\"";
result.Append(delimiter).Append(col);
delimiter = ",";
}
result.AppendLine();
}
return result.ToString();
}
Additionally, I'm very concerned about the SelectData() method. Code like this tends to force you to write code with HUGE GAPING SQL INJECTION VULNERABILITIES... the kind of thing where a year from now you finally find out your app was hacked six months ago, all your user data was leaked to the dark web, and your internal systems are now being held for a bitcoin ransom.

Exporting DataGrid to CSV without a GUI in WPF

I have an application that runs with and without a GUI depending on the user. I need it to export the DataGrid as a CSV file, heres the code:
// Copy contents of datagrid to clipboard, including header.
mainDataGrid.SelectAllCells();
mainDataGrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
ApplicationCommands.Copy.Execute(null, mainDataGrid);
string result = (string)Clipboard.GetData(DataFormats.CommaSeparatedValue);
This works fine with the GUI. The problem happens when I don't have a GUI, I think this is because the clipboard can't copy something that isn't there. Is there a different way to export it or is there a way to set the clipboards data instead of executing a copy command?
In order to "copy something that isn't there", you'll need to use the underlying data object that is your DataGrid's ItemsSource.
If your DataGrid's ItemsSource is a DataTable, this method, analogous to MosesTheHoly's, will return a CSV string, but in a simpler manner.
public string GetCSVFromDataTabe(DataTable datatable)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(string.Join(",", dt.Columns.ToList<DataColumn>().Select(column => column.ColumnName).ToList()) + "\n");
dataTable.Rows.ToList<DataRow>().ForEach(row => sb.Append(string.Join(",", row.ItemArray) + "\n"));
return stringBuilder.ToString();
}
That should do it. Fairly concise, in my opinion. Now, just write that string to whatever file you need.
Alright, the code could probably be shortened, but this is the way I did it. I created a function that takes in a datatable and returns a CSV string.
private void SaveToCSV() {
DataTable dt = new DataTable();
dt = ((DataView)mainDataGrid.ItemsSource).ToTable();
string result = WriteDataTable(dt);
// The File.Create().Close() is so it closes the filestream after it creates it.
if (!File.Exists(CSVFilePath)) {
File.Create(CSVFilePath).Close();
}
File.AppendAllText(CSVFilePath, result, UnicodeEncoding.UTF8);
}
private string WriteDataTable(DataTable dataTable) {
string output = "";
// Need to get the last column so I know when to add a new line instead of comma.
string lastColumnName = dataTable.Columns[dataTable.Columns.Count - 1].ColumnName;
// Get the headers from the datatable.
foreach (DataColumn column in dataTable.Columns) {
if (lastColumnName != column.ColumnName) {
output += (column.ColumnName.ToString() + ",");
}
else {
output += (column.ColumnName.ToString() + "\n");
}
}
// Get the actual data from the datatable.
foreach (DataRow row in dataTable.Rows) {
foreach (DataColumn column in dataTable.Columns) {
if (lastColumnName != column.ColumnName) {
output += (row[column].ToString() + ",");
}
else {
output += (row[column].ToString() + "\n");
}
}
}
return output;
}

Parsing CSV file with type

I am trying to parse a CSV and construct a DataTable out of it. Now the tricky part is i would like to assign data types before constructing the data table.
For eg consider the following CSV file
Name,Age,Salary
A,30,1000
B,35,1500
C,40,2000
I would like to have Name stored as string, Age as Int and Salary as decimal in the data table I am constructing. Any suggestions on the best way to do this?
Here's a naive implementation that ignores most error checking, and some good coding practices:
namespace StackOverflowConsole
{
using System;
using System.IO;
using System.Data;
class Program
{
static void Main(string[] args)
{
var path = #"C:\temp\test.csv";
CreateTestFile(path);
var dataTable = new DataTable();
dataTable.Columns.Add("Name", typeof(string));
dataTable.Columns.Add("Age", typeof(int));
dataTable.Columns.Add("Salary", typeof(decimal));
// TODO: add checks, exception handling
using (var reader = new StreamReader(path))
{
// reads all lines into a single string
var lines = reader.ReadToEnd().Split(new char[] { '\n' });
if (lines.Length > 0)
{
// you may wanna skip the first line, if you're using a file header
foreach (string line in lines)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
// split the current line using the separator
var tokens = line.Trim().Split(new char[] { ',' });
// check your assumptions on the CSV contents
// ex: only process lines with the correct number of fields
if (tokens.Length == 3)
{
var person = new Person();
person.Name = tokens[0];
// a better implementation would use TryParse()
person.Age = Int32.Parse(tokens[1]);
person.Salary = Decimal.Parse(tokens[2]);
dataTable.Rows.Add(person.Name, person.Age, person.Salary);
}
}
}
}
}
private static void CreateTestFile(string path)
{
if (File.Exists(path))
{
File.Delete(path);
}
using (var writer = new StreamWriter(path))
{
writer.WriteLine("A,30,1000");
writer.WriteLine("B,35,1500");
writer.WriteLine("C,40,2000");
}
}
}
public class Person
{
public string Name;
public int Age;
public decimal Salary;
}
}
Try this:
Keep CSV file in code directory
string path = Server.MapPath("emp.csv");
string header = "Yes";
string sql = string.Empty;
DataTable dt = null;
string fullpath = Path.GetDirectoryName(path);
string fileName = Path.GetFileName(path);
OleDbConnection connection = new OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fullpath + ";Extended Properties=\"Text;HDR=" + header + "\"");
OleDbDataAdapter da = new OleDbDataAdapter("select * from [" + fileName + "]", connection);
dt = new DataTable();
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("Age", typeof(int));
dt.Columns.Add("Salary", typeof(decimal));
da.Fill(dt);
GridView1.DataSource = dt;
GridView1.DataBind();

CSV parser to parse double quotes via OLEDB

How can I use OLEDB to parse and import a CSV file that each cell is encased in double quotes because some rows contain commas in them?? I am unable to change the format as it is coming from a vendor.
I am trying the following and it is failing with an IO error:
public DataTable ConvertToDataTable(string fileToImport, string fileDestination)
{
string fullImportPath = fileDestination + #"\" + fileToImport;
OleDbDataAdapter dAdapter = null;
DataTable dTable = null;
try
{
if (!File.Exists(fullImportPath))
return null;
string full = Path.GetFullPath(fullImportPath);
string file = Path.GetFileName(full);
string dir = Path.GetDirectoryName(full);
//create the "database" connection string
string connString = "Provider=Microsoft.Jet.OLEDB.4.0;"
+ "Data Source=\"" + dir + "\\\";"
+ "Extended Properties=\"text;HDR=No;FMT=Delimited\"";
//create the database query
string query = "SELECT * FROM " + file;
//create a DataTable to hold the query results
dTable = new DataTable();
//create an OleDbDataAdapter to execute the query
dAdapter = new OleDbDataAdapter(query, connString);
//fill the DataTable
dAdapter.Fill(dTable);
}
catch (Exception ex)
{
throw new Exception(CLASS_NAME + ".ConvertToDataTable: Caught Exception: " + ex);
}
finally
{
if (dAdapter != null)
dAdapter.Dispose();
}
return dTable;
}
When I use a normal CSV it works fine. Do I need to change something in the connString??
Use a dedicated CSV parser.
There are many out there. A popular one is FileHelpers, though there is one hidden in the Microsoft.VisualBasic.FileIO namespace - TextFieldParser.
Have a look at FileHelpers.
You can use this code : MS office required
private void ConvertCSVtoExcel(string filePath = #"E:\nucc_taxonomy_140.csv", string tableName = "TempTaxonomyCodes")
{
string tempPath = System.IO.Path.GetDirectoryName(filePath);
string strConn = #"Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" + tempPath + #"\;Extensions=asc,csv,tab,txt";
OdbcConnection conn = new OdbcConnection(strConn);
OdbcDataAdapter da = new OdbcDataAdapter("Select * from " + System.IO.Path.GetFileName(filePath), conn);
DataTable dt = new DataTable();
da.Fill(dt);
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(ConfigurationSettings.AppSettings["dbConnectionString"]))
{
bulkCopy.DestinationTableName = tableName;
bulkCopy.BatchSize = 50;
bulkCopy.WriteToServer(dt);
}
}
There is a lot to consider when handling CSV files. However you extract them from the file, you should know how you are handling the parsing. There are classes out there that can get you part way, but most don't handle the nuances that Excel does with embedded commas, quotes and line breaks. However, loading Excel or the MS classes seems a lot of freaking overhead if you just want parse a txt file like a CSV.
One thing you can consider is doing the parsing in your own Regex, which will also make your code a little more platform independent, in case you need to port it to another server or application at some point. Using regex has the benefit of also being accessible in virtually every language. That said, there are some good regex patterns out there that handle the CSV puzzle. Here is my shot at it, which does cover embedded commas, quotes and line breaks. Regex code/pattern and explanation :
http://www.kimgentes.com/worshiptech-web-tools-page/2008/10/14/regex-pattern-for-parsing-csv-files-with-embedded-commas-dou.html
Hope that is of some help..
Try the code from my answer here:
Reading CSV files in C#
It handles quoted csv just fine.
private static void Mubashir_CSVParser(string s)
{
// extract the fields
Regex RegexCSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
String[] Fields = RegexCSVParser.Split(s);
// clean up the fields (remove " and leading spaces)
for (int i = 0; i < Fields.Length; i++)
{
Fields[i] = Fields[i].TrimStart(' ', '"');
Fields[i] = Fields[i].TrimEnd('"');// this line remove the quotes
//Fields[i] = Fields[i].Trim();
}
}
Just incase anyone has a similar issue, i wanted to post the code i used. i did end up using Textparser to get the file and parse ot the columns, but i am using recrusion to get the rest done and substrings.
/// <summary>
/// Parses each string passed as a "row".
/// This routine accounts for both double quotes
/// as well as commas currently, but can be added to
/// </summary>
/// <param name="row"> string or row to be parsed</param>
/// <returns></returns>
private List<String> ParseRowToList(String row)
{
List<String> returnValue = new List<String>();
if (row[0] == '\"')
{// Quoted String
if (row.IndexOf("\",") > -1)
{// There are more columns
returnValue = ParseRowToList(row.Substring(row.IndexOf("\",") + 2));
returnValue.Insert(0, row.Substring(1, row.IndexOf("\",") - 1));
}
else
{// This is the last column
returnValue.Add(row.Substring(1, row.Length - 2));
}
}
else
{// Unquoted String
if (row.IndexOf(",") > -1)
{// There are more columns
returnValue = ParseRowToList(row.Substring(row.IndexOf(",") + 1));
returnValue.Insert(0, row.Substring(0, row.IndexOf(",")));
}
else
{// This is the last column
returnValue.Add(row.Substring(0, row.Length));
}
}
return returnValue;
}
Then the code for Textparser is:
// string pathFile = #"C:\TestFTP\TestCatalog.txt";
string pathFile = #"C:\TestFTP\SomeFile.csv";
List<String> stringList = new List<String>();
TextFieldParser fieldParser = null;
DataTable dtable = new DataTable();
/* Set up TextFieldParser
* use the correct delimiter provided
* and path */
fieldParser = new TextFieldParser(pathFile);
/* Set that there are quotes in the file for fields and or column names */
fieldParser.HasFieldsEnclosedInQuotes = true;
/* delimiter by default to be used first */
fieldParser.SetDelimiters(new string[] { "," });
// Build Full table to be imported
dtable = BuildDataTable(fieldParser, dtable);
This is what I used in a project, parses a single line of data.
private string[] csvParser(string csv, char separator = ',')
{
List <string> parsed = new List<string>();
string[] temp = csv.Split(separator);
int counter = 0;
string data = string.Empty;
while (counter < temp.Length)
{
data = temp[counter].Trim();
if (data.Trim().StartsWith("\""))
{
bool isLast = false;
while (!isLast && counter < temp.Length)
{
data += separator.ToString() + temp[counter + 1];
counter++;
isLast = (temp[counter].Trim().EndsWith("\""));
}
}
parsed.Add(data);
counter++;
}
return parsed.ToArray();
}
http://zamirsblog.blogspot.com/2013/09/c-csv-parser-csvparser.html

How can I turn a DataTable to a CSV?

Could somebody please tell me why the following code is not working. The data is saved into the csv file, however the data is not separated. It all exists within the first cell of each row.
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());
Thanks.
The following shorter version opens fine in Excel, maybe your issue was the trailing comma
.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
And as Tim pointed out, if you are on .net>=4, you can make it even shorter:
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());
As suggested by Christian, if you want to handle special characters escaping in fields, replace the loop block by:
foreach (DataRow row in dt.Rows)
{
IEnumerable<string> fields = row.ItemArray.Select(field =>
string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
sb.AppendLine(string.Join(",", fields));
}
And last suggestion, you could write the csv content line by line instead of as a whole document, to avoid having a big document in memory.
I wrapped this up into an extension class, which allows you to call:
myDataTable.WriteToCsvFile("C:\\MyDataTable.csv");
on any 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());
}
}
A new extension function based on Paul Grimshaw's answer. I cleaned it up and added the ability to handle unexpected data. (Empty Data, Embedded Quotes, and comma's in the headings...)
It also returns a string which is more flexible. It returns Null if the table object does not contain any structure.
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();
}
You call it as follows:
var csvData = dataTableOject.ToCsv();
If your calling code is referencing the System.Windows.Forms assembly, you may consider a radically different approach.
My strategy is to use the functions already provided by the framework to accomplish this in very few lines of code and without having to loop through columns and rows. What the code below does is programmatically create a DataGridView on the fly and set the DataGridView.DataSource to the DataTable. Next, I programmatically select all the cells (including the header) in the DataGridView and call DataGridView.GetClipboardContent(), placing the results into the Windows Clipboard. Then, I 'paste' the contents of the clipboard into a call to File.WriteAllText(), making sure to specify the formatting of the 'paste' as TextDataFormat.CommaSeparatedValue.
Here is the code:
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);
}
}
}
Notice I also make sure to preserve the contents of the clipboard before I begin, and restore it once I'm done, so the user does not get a bunch of unexpected garbage next time the user tries to paste. The main caveats to this approach is 1) Your class has to reference System.Windows.Forms, which may not be the case in a data abstraction layer, 2) Your assembly will have to be targeted for .NET 4.5 framework, as DataGridView does not exist in 4.0, and 3) The method will fail if the clipboard is being used by another process.
Anyways, this approach may not be right for your situation, but it is interesting none the less, and can be another tool in your toolbox.
I did this recently but included double quotes around my values.
For example, change these two lines:
sb.Append("\"" + col.ColumnName + "\",");
...
sb.Append("\"" + row[i].ToString() + "\",");
Try changing 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());
4 lines of code:
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();
}
Note that the ToList() at the end is important; I need something to force an expression evaluation. If I was code golfing, I could use Min() instead.
Also note that the result will have a newline at the end because of the last call to AppendLine(). You may not want this. You can simply call TrimEnd() to remove it.
Try to put ; instead of ,
Hope it helps
The error is the list separator.
Instead of writing sb.Append(something... + ',') you should put something like sb.Append(something... + System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);
You must put the list separator character configured in your operating system (like in the example above), or the list separator in the client machine where the file is going to be watched. Another option would be to configure it in the app.config or web.config as a parammeter of your application.
To write to a file, I think the following method is the most efficient and straightforward: (You can add quotes if you want)
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));
}
}
}
Read this and this?
A better implementation would be
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());
To mimic 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();
}
Here is an enhancement to vc-74's post that handles commas the same way Excel does. Excel puts quotes around data if the data has a comma but doesn't quote if the data doesn't have a comma.
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;
}
Here is my solution, based on previous answers by Paul Grimshaw and Anthony VO.
I've submitted the code in a C# project on Github.
My main contribution is to eliminate explicitly creating and manipulating a StringBuilder and instead working only with IEnumerable. This avoids the allocation of a big buffer in memory.
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.Contains('\r') || self.Contains('\n') || 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());
}
}
This approach combines nicely with converting IEnumerable to DataTable as asked here.
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();
}
Possibly, most easy way will be to use:
https://github.com/ukushu/DataExporter
especially in case of your data of datatable containing /r/n characters or separator symbol inside of your dataTable cells. Almost all of other answers will not work with such cells.
only you need is to write the following code:
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();
Most existing answers can easily cause OutOfMemoryException, so I decided to write my own answer.
DON' T DO THIS:
using a DataSet + StringBuilder causes the data to occupy the memory 3x at once:
Load All Data into DataSet
Copy all data into StringBuilder
Copy the data to string using StringBuilder.ToString();
Instead you should write each row to a FileStream separately. There is no need to create the whole CSV in memory.
Even better, use a DataReader instead DataSet. That way you can read from database billions of records one by one a write the to a file one by one.
If you don't mind using an external library for CSV, I can recommend the most popular CsvHelper, which has no dependencies.
using (var writer = new FileWriter("test.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
foreach (DataColumn dc in dt.Columns)
{
csv.WriteField(dc.ColumnName);
}
csv.NextRecord();
foreach (DataRow dr in dt.Rows)
{
foreach (DataColumn dc in dt.Columns)
{
csv.WriteField(dr[dc]);
}
csv.NextRecord();
}
writer.ToString().Dump();
}
In case anyone else stumbles on this, I was using File.ReadAllText to get CSV data and then I modified it and wrote it back with File.WriteAllText. The \r\n CRLFs were fine but the \t tabs were ignored when Excel opened it. (All solutions in this thread so far use a comma delimiter but that doesn't matter.) Notepad showed the same format in the resulting file as in the source. A Diff even showed the files as identical. But I got a clue when I opened the file in Visual Studio with a binary editor. The source file was Unicode but the target was ASCII. To fix, I modified both ReadAllText and WriteAllText with third argument set as System.Text.Encoding.Unicode, and from there Excel was able to open the updated file.

Categories

Resources