Exporting datagridview to csv file - c#

I'm working on a application which will export my DataGridView called scannerDataGridView to a csv file.
Found some example code to do this, but can't get it working. Btw my datagrid isn't databound to a source.
When i try to use the Streamwriter to only write the column headers everything goes well, but when i try to export the whole datagrid including data i get an exeption trhown.
System.NullReferenceException: Object reference not set to an instance
of an object. at Scanmonitor.Form1.button1_Click(Object sender,
EventArgs e)
Here is my Code, error is given on the following line:
dataFromGrid = dataFromGrid + ',' + dataRowObject.Cells[i].Value.ToString();
//csvFileWriter = StreamWriter
//scannerDataGridView = DataGridView
private void button1_Click(object sender, EventArgs e)
{
string CsvFpath = #"C:\scanner\CSV-EXPORT.csv";
try
{
System.IO.StreamWriter csvFileWriter = new StreamWriter(CsvFpath, false);
string columnHeaderText = "";
int countColumn = scannerDataGridView.ColumnCount - 1;
if (countColumn >= 0)
{
columnHeaderText = scannerDataGridView.Columns[0].HeaderText;
}
for (int i = 1; i <= countColumn; i++)
{
columnHeaderText = columnHeaderText + ',' + scannerDataGridView.Columns[i].HeaderText;
}
csvFileWriter.WriteLine(columnHeaderText);
foreach (DataGridViewRow dataRowObject in scannerDataGridView.Rows)
{
if (!dataRowObject.IsNewRow)
{
string dataFromGrid = "";
dataFromGrid = dataRowObject.Cells[0].Value.ToString();
for (int i = 1; i <= countColumn; i++)
{
dataFromGrid = dataFromGrid + ',' + dataRowObject.Cells[i].Value.ToString();
csvFileWriter.WriteLine(dataFromGrid);
}
}
}
csvFileWriter.Flush();
csvFileWriter.Close();
}
catch (Exception exceptionObject)
{
MessageBox.Show(exceptionObject.ToString());
}

LINQ FTW!
var sb = new StringBuilder();
var headers = dataGridView1.Columns.Cast<DataGridViewColumn>();
sb.AppendLine(string.Join(",", headers.Select(column => "\"" + column.HeaderText + "\"").ToArray()));
foreach (DataGridViewRow row in dataGridView1.Rows)
{
var cells = row.Cells.Cast<DataGridViewCell>();
sb.AppendLine(string.Join(",", cells.Select(cell => "\"" + cell.Value + "\"").ToArray()));
}
And indeed, c.Value.ToString() will throw on null value, while c.Value will correctly convert to an empty string.

A little known feature of the DataGridView is the ability to programmatically select some or all of the DataGridCells, and send them to a DataObject using the method DataGridView.GetClipboardContent(). Whats the advantage of this then?
A DataObject doesn't just store an object, but rather the representation of that object in various different formats. This is how the Clipboard is able to work its magic; it has various formats stored and different controls/classes can specify which format they wish to accept. In this case, the DataGridView will store the selected cells in the DataObject as a tab-delimited text format, a CSV format, or as HTML (*).
The contents of the DataObject can be retrieved by calling the DataObject.GetData() or DataObject.GetText() methods and specifying a predefined data format enum. In this case, we want the format to be TextDataFormat.CommaSeparatedValue for CSV, then we can just write that result to a file using System.IO.File class.
(*) Actually, what it returns is not, strictly speaking, HTML. This format will also contain a data header that you were not expecting. While the header does contain the starting position of the HTML, I just discard anything above the HTML tag like myString.Substring(IndexOf("<HTML>"));.
Observe the following code:
void SaveDataGridViewToCSV(string filename)
{
// Choose whether to write header. Use EnableWithoutHeaderText instead to omit header.
dataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
// Select all the cells
dataGridView1.SelectAll();
// Copy selected cells to DataObject
DataObject dataObject = dataGridView1.GetClipboardContent();
// Get the text of the DataObject, and serialize it to a file
File.WriteAllText(filename, dataObject.GetText(TextDataFormat.CommaSeparatedValue));
}
Now, isn't that better? Why re-invent the wheel?
Hope this helps...

Please check this code.its working fine
try
{
//Build the CSV file data as a Comma separated string.
string csv = string.Empty;
//Add the Header row for CSV file.
foreach (DataGridViewColumn column in dataGridView1.Columns)
{
csv += column.HeaderText + ',';
}
//Add new line.
csv += "\r\n";
//Adding the Rows
foreach (DataGridViewRow row in dataGridView1.Rows)
{
foreach (DataGridViewCell cell in row.Cells)
{
if (cell.Value != null)
{
//Add the Data rows.
csv += cell.Value.ToString().TrimEnd(',').Replace(",", ";") + ',';
}
// break;
}
//Add new line.
csv += "\r\n";
}
//Exporting to CSV.
string folderPath = "C:\\CSV\\";
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
File.WriteAllText(folderPath + "Invoice.csv", csv);
MessageBox.Show("");
}
catch
{
MessageBox.Show("");
}

Found the problem, the coding was fine but i had an empty cell that gave the problem.

Your code was almost there... But I made the following corrections and it works great. Thanks for the post.
Error:
string[] output = new string[dgvLista_Apl_Geral.RowCount + 1];
Correction:
string[] output = new string[DGV.RowCount + 1];
Error:
System.IO.File.WriteAllLines(filename, output, System.Text.Encoding.UTF8);
Correction:
System.IO.File.WriteAllLines(sfd.FileName, output, System.Text.Encoding.UTF8);

The line "csvFileWriter.WriteLine(dataFromGrid);" should be moved down one line below the closing bracket, else you'll get a lot of repeating results:
for (int i = 1; i <= countColumn; i++)
{
dataFromGrid = dataFromGrid + ',' + dataRowObject.Cells[i].Value.ToString();
}
csvFileWriter.WriteLine(dataFromGrid);

I think this is the correct for your SaveToCSV function : ( otherwise Null ...)
for (int i = 0; i < columnCount; i++)
Not :
for (int i = 1; (i - 1) < DGV.RowCount; i++)

This is what I been using in my projects:
void export_csv(string file, DataGridView grid)
{
using (StreamWriter csv = new StreamWriter(file, false))
{
int totalcolms = grid.ColumnCount;
foreach (DataGridViewColumn colm in grid.Columns) csv.Write(colm.HeaderText + ',');
csv.Write('\n');
string data = "";
foreach (DataGridViewRow row in grid.Rows)
{
if (row.IsNewRow) continue;
data = "";
for (int i = 0; i < totalcolms; i++)
{
data += (row.Cells[i].Value ?? "").ToString() + ',';
}
if (data != string.Empty) csv.WriteLine(data);
}
}
}

Related

Remove last (empty) entry from StringBuilder object, when exporting to CSV

I want to remove the last (empty) row of a StringBuilder Object
EDIT: The empty row is from the "AllowUserToAddRows" how can i skip it?
c# Forms application
dataGridView1 on form2
I want Export to CSV (separated by semicolon) [btw. it's just one column]
It Could happen, that a previously created CSV is parsed to the dataGridView again
I use a altered solution from here:
Exporting datagridview to csv file
my code
void SaveDataGridViewToCSV(string filename)
{
var sb = new StringBuilder();
var headers = dataGridView1.Columns.Cast<DataGridViewColumn>();
sb.AppendLine(string.Join(";", headers.Select(column => "" + column.HeaderText + ";").ToArray()));
foreach (DataGridViewRow row in dataGridView1.Rows)
{
var cells = row.Cells.Cast<DataGridViewCell>();
sb.AppendLine(string.Join(";", cells.Select(cell => "" + cell.Value + ";").ToArray()));
}
try
{
File.WriteAllText(filename, sb.ToString());
}
catch (Exception exceptionObject)
{
MessageBox.Show(exceptionObject.ToString());
}
}
The sb.ToString looks like this {Coumn;90;90;626;626;;}
The "real" StringBuilder Object as String is: {Coumn;\r\n90;\r\n90;\r\n626;\r\n626;\r\n;}
I want to remove the empty last row.
I tried to parse the stringbuilder to a string, and then remove last semicolon
but with no success (i have problem with the End Of Line.)
string s = sb.ToString();
while (s.EndsWith(";\r\n;") == true)
{
s.Substring(0, s.Length - 5);
}
I tried to remove last element of array, but StingBuilder is no array
I'm stuck.
As i found out the empty set is always exported to the csv.
It's from the ability that the user can input data to the dataGridView, and there is always a empty, active row.
if i disable dataGridView1.AllowUserToAddRows the empty row for userinput is not within the stringbuilder set of data.
void SaveDataGridViewToCSV(string filename)
{
var sb = new StringBuilder();
//SOLUTION disable AllowUserInput to avoid empty set saved to CSV
dataGridView1.AllowUserToAddRows = false;
var headers = dataGridView1.Columns.Cast<DataGridViewColumn>();
sb.AppendLine(string.Join(";", headers.Select(column => "" + column.HeaderText + ";").ToArray()));
foreach (DataGridViewRow row in dataGridView1.Rows)
{
var cells = row.Cells.Cast<DataGridViewCell>();
sb.AppendLine(string.Join(";", cells.Select(cell => "" + cell.Value + ";").ToArray()));
}
try
{
File.WriteAllText(filename, sb.ToString());
}
catch (Exception exceptionObject)
{
MessageBox.Show(exceptionObject.ToString());
}
}
thanks for help!

Transfer text file items with degree symbol to datagridview

I am trying to input items in datagridview using text file.
I have below code to transfer the values of a csv file to my datagridview.
private void TransfertoDataGridView()
{
try
{
string SelectedReport = ReportsFilename;
System.IO.StreamReader file = new System.IO.StreamReader(SelectedReport);
string[] columnnames = file.ReadLine().Split(',');
DataTable dt = new DataTable();
foreach (string c in columnnames)
{
dt.Columns.Add(c);
}
dataGridView1.DataSource = null;
string newline;
while ((newline = file.ReadLine()) != null)
{
if (newline != "")
{
DataRow dr = dt.NewRow();
string[] values = newline.Split(',');
for (int i = 0; i < values.Length; i++)
{
try
{
dr[i] = values[i];
}
catch { }
}
dt.Rows.Add(dr);
}
}
file.Close();
dataGridView1.DataSource = dt;
this.dataGridView1.Columns[0].Visible = false;
}
catch
{
dataGridView1.DataSource = null;
}
}
Below is how my csv file looks like,
Column D has items with degree symbol.
And when I run the code, it transfers all the cell values but replaces the degree symbol with question mark "?".
I need expert help on how I can input the degree symbol to the datagridview.
Thank you very much in advanced for the help.
You can use something like this:
string str = "this is my string with a � sign like this.";
str = str.Replace('�', '°');
As a result:
this is my string with a ° sign like this.
If String like � then it will replace a ° sign.
Any symbol I guess you can do it with a replace function.

Delete row in a CSV file and show in DataGridView using c#

I have a problem when I want to delete a row in a CSV File, I have this code but only deletes the field that contains the line.
Example:
CSV File:
ID,Name,Lastname,Country
1,David,tod,UK
2,Juan,Perez,Germ
3,Pepe,Lopez,Col
First iteration, sending the id 1 to delete the line:
ID,Name,Lastname,Country
David,tod,UK
2,Juan,Perez,Germ
3,Pepe,Lopez,Arg
Just delete the id I want, but not the whole line
The expected result would be that like this:
ID,Name,Lastname,Country
2,Juan,Perez,Arg
3,Pepe,Lopez,Col
this is my code, What am I doing wrong? I have never used csv in C# :(
string searchid = "1";
string[] values = File.ReadAllText("C:\\registros.csv").Split(new char[] { ',' });
StringBuilder ObjStringBuilder = new StringBuilder();
for (int i = 0; i < values.Length; i++)
{
if (values[i].Contains(searchid))
continue;
ObjStringBuilder.Append(values[i] + ",");
}
ObjStringBuilder.ToString().Remove(ObjStringBuilder.Length - 1);
File.WriteAllText("\\registros.csv", ObjStringBuilder.ToString());
Another question is how can I show the CSV file in a datagridview in Windows Forms. I have this logic, don't know if this is correct, but how I can show it?
public DataTable ConvertCSVtoDataTable()
{
StreamReader sr = new StreamReader("\\registros.csv");
string[] headers = sr.ReadLine().Split(',');
DataTable dt = new DataTable();
foreach (string header in headers)
{
dt.Columns.Add(header);
}
while (!sr.EndOfStream)
{
string[] rows = Regex.Split(sr.ReadLine(), ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
DataRow dr = dt.NewRow();
for (int i = 0; i < headers.Length; i++)
{
dr[i] = rows[i];
}
dt.Rows.Add(dr);
}
return dt;
}
Thanks!
You can delete row from CSV using below link
Delete rows from CSV
and
You can convert the CSV into DataTable using the below code. If your csv file uses delimiter as ,
public DataTable ReadCSV(String FilePath, Boolean IsHeader)
{
string strConn = null;
string folderpath = null;
try
{
folderpath = FilePath.Substring(0, FilePath.LastIndexOf("\\") + 1);
string FileName = Path.GetFileName(FilePath);
if (IsHeader == true)
{
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + folderpath + ";" + "Extended Properties=\"text;HDR=YES\"";
}
else
{
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + folderpath + ";" + "Extended Properties=\"text;HDR=NO\"";
}
OleDbConnection Conn = new OleDbConnection();
Conn.ConnectionString = strConn;
Conn.Open();
string s1 = "select * from [" + FileName + "]";
OleDbDataAdapter da1 = new OleDbDataAdapter(s1, Conn);
DataSet dtall = new DataSet();
da1.Fill(dtall);
Conn.Close();
return dtall.Tables[0].Copy();
}
catch (Exception ex)
{
Exception excep = new Exception("CSV : " + ex.Message);
throw excep;
}
}
Reading and writing CSV files is not as trivial as it first seems. Cells can have embedded commas, and even new line characters. The following is one implementation of a CSV reader which can optionally be run asynchronously as a background worker. This implementation returns a standard DataTable which can easily be bound to a DataGridView:
grid.DataSource = dataTable;
The CsvReader class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
namespace CsvReaderExample
{
public class CsvReader
: BackgroundWorker
{
string[] m_lines;
public DataTable DataTable { get; private set; }
public CsvReader(string[] lines)
{
m_lines = lines;
WorkerReportsProgress = true;
WorkerSupportsCancellation = true;
}
public DataTable RunWorker()
{
return DataTable = ParseCsvLines();
}
protected override void OnDoWork(DoWorkEventArgs e)
{
base.OnDoWork(e);
e.Result = DataTable = ParseCsvLines();
}
private DataTable ParseCsvLines()
{
if (m_lines.Length == 0)
return null;
var table = new DataTable();
var columns = table.Columns;
var columnNames = GetRowValues(m_lines[0]);
foreach (var columnName in columnNames)
{
var name = columnName;
int number = 2;
while (columns.Contains(name))
name += " " + number++;
columns.Add(name);
}
var rows = table.Rows;
for (int index = 1, linesCount = m_lines.Length; index < linesCount; index++)
{
if (CancellationPending)
return null;
var line = m_lines[index];
var values = GetRowValues(line);
int valueCount = values.Count;
if (valueCount > columns.Count)
{
int columnNumber = columns.Count;
while (columns.Contains(columnNumber.ToString()))
columnNumber++;
columns.Add(columnNumber.ToString());
}
rows.Add(values.ToArray());
if (WorkerReportsProgress)
ReportProgress(100 * index / linesCount);
}
return table;
}
const char COMMA = ',',
DOUBLE_QUOTE = '"',
VERTICAL_BAR = '|';
private List<string> GetRowValues(string line)
{
var builder = new StringBuilder();
var values = new List<string>();
var inDoubleQuotes = false;
var maxIndex = line.Length - 1;
for (int index = 0; index <= maxIndex; index++)
{
char c = line[index];
if (c == DOUBLE_QUOTE)
{
if (index == 0)
{
inDoubleQuotes = true;
continue;
}
if (index < maxIndex)
{
var nextIndex = index + 1;
if (nextIndex < maxIndex)
{
if (line[nextIndex] == DOUBLE_QUOTE)
{
index++;
if (inDoubleQuotes)
builder.Append(DOUBLE_QUOTE);
continue;
}
}
}
inDoubleQuotes = !inDoubleQuotes;
continue;
}
if (c == COMMA)
{
if (inDoubleQuotes)
{
builder.Append(c);
continue;
}
values.Add(builder.ToString());
builder = new StringBuilder();
continue;
}
builder.Append(c);
}
values.Add(builder.ToString());
return values;
}
#region Sanitise cells with new line characters
public static void SanitiseCellsWithNewLineCharacters(string fileName)
{
var text = File.ReadAllText(fileName, Encoding.Default);
text = text.Replace("\r\n", "\n");
text = text.Replace("\r", "\n");
using (var writer = File.CreateText(fileName))
{
var inDoubleQuotes = false;
foreach (char c in text)
{
if (c == '\n' && inDoubleQuotes)
{
writer.Write(VERTICAL_BAR);
continue;
}
if (c == DOUBLE_QUOTE)
{
if (inDoubleQuotes)
inDoubleQuotes = false;
else
inDoubleQuotes = true;
}
writer.Write(c);
}
}
}
#endregion
}
}
You can read the DataTable synchronously as follows:
var lines = File.ReadAllLines("C:\\registros.csv");
var csvReader = new CsvReader(lines);
var dataTable = csvReader.RunWorker();
You could then remove row(s) from the DataTable with a method such as:
private static void RemoveById(DataTable dataTable, int id)
{
var column = dataTable.Columns["ID"];
if (column == null)
return;
var rows = dataTable.Rows;
for (int index = rows.Count - 1; index >= 0; index--)
{
var row = rows[index];
var value = row ["ID"];
if (value == null)
continue;
if (value.Equals(id))
{
rows.RemoveAt(index);
return;
}
}
}
Call it:
RemoveById(dataTable, 1);
The first thing that is wrong with your implementation is that you use ',' as the separator. You should either split on the new-line character '\n' or read the file line by line as follows:
var lines = new List<string>();
var file = new System.IO.StreamReader("c:\\registros.csv");
string line;
while((line = file.ReadLine()) != null)
{
lines.Add(line);
}
file.Close();
You could then look for the line that starts with the id you are looking for. When you find it, remove the line from the list.
for(int i=0; i++; i<lines.Count)
{
if (lines[i].StartsWith(searchid))
{
lines.RemoveAt(i);
break;
}
}
Next step is to write the result back to the file:
File.WriteAllLines("c:\\registros.csv", lines);
Regarding your second question, I found a similar Q/A on stackoverflow here.
First step is creating the DataTable, then you'll have to bind the table to the table control that will show the data.
SIMPLE & UNDERSTANDABLE!`
Solution For your First Problem is:
****Reading & Writing back to CSV File!****
string searchid = "1";
string[] values = File.ReadAllText(#"Complete Path Of File").Split(new char[] { '\n' });
StringBuilder ObjStringBuilder = new StringBuilder();
for (int i = 0; i < values.Length - 1; i++)
{
if (values[i].StartsWith(searchid) == false)
{
ObjStringBuilder.Append(values[i]+"\n");
}
}
File.WriteAllText(#"Complete Path Of File", ObjStringBuilder.ToString());
}
Answer to your Second Doubt:
****Populating DataGridView dynamically from CSV File!****
Comma(,) Problem SOLVED:
DataTable dtDataSource = new DataTable();
string[] fileContent = File.ReadAllLines(#"..\\Book1.csv");
if (fileContent.Count() > 0)
{
//Create data table columns dynamically
string[] columns = fileContent[0].Split(',');
for (int i = 0; i < columns.Count(); i++)
{
dtDataSource.Columns.Add(columns[i]);
}
//Add row data dynamically
for (int i = 1; i < fileContent.Count(); i++)
{
string[] rowData = fileContent[i].Split(',');
string[] realRowData = new string[columns.Count()];
StringBuilder collaboration = new StringBuilder();
int v = 0;
//this region solves the problem of a cell containing ",".
#region CommaSepProblem
for (int j = 0, K = 0; j < rowData.Count(); j++, K++)
{
//After splitting the line with commas. The cells containing commas will also be splitted.
//Fact: if a cell contains special symbol in excel that cell will be saved in .csv contained in quotes E.g A+B will be saved "A+B" or A,B will be saved as "A,B"
//Our code splits everything where comma is found. So solution is:
//Logic: After splitting if a string contains even number of DoubleQuote then its perfect cell otherwise, it is splitted in multiple cells of array.
if ((rowData[j].Count(x => x == '"') % 2 == 0))//checks if the string contains even number of DoubleQuotes
{
realRowData[K] = quotesLogic((rowData[j]));
}
else if ((rowData[j].Count(x => x == '"') % 2 != 0))//If Number of DoubleQuotes are ODD
{
int c = rowData[j].Count(x => x == '"');
v = j;
while (c % 2 != 0)//Go through all the next array cell till it makes EVEN Number of DoubleQuotes.
{
collaboration.Append(rowData[j] + ",");
j++;
c += rowData[j].Count(x => x == '"');
}
collaboration.Append(rowData[j]);
realRowData[K] = quotesLogic(collaboration.ToString());
}
else { continue; }
}
#endregion
dtDataSource.Rows.Add(realRowData);
}
if (dtDataSource != null)
{
dataGrid1.ItemsSource = dtDataSource.DefaultView;
}
}
Add This Method Too:
string quotesLogic(string collaboration)
{
StringBuilder after = new StringBuilder(collaboration);
if (after.ToString().StartsWith("\"") && after.ToString().EndsWith("\""))//removes 1st and last quotes as those are system generated
{
after.Remove(0, 1);
after.Remove(after.Length - 1, 1);
int count = after.Length - 1;
//FACT: if you try to add DoubleQuote in a cell in excel. It'll save that quote as 2 times DoubleQuote(Like "") which means first DoubleQuote is to give instruction to CPU that the next DoubleQuote is not system generated.
while (count > 0)//This loop find twice insertion of 2 DoubleQuotes and neutralise them to One DoubleQuote.
{
if (after[count] == '"' && after[count - 1] == '"')
{
after.Remove(count, 1);
}
count--;
}
}
return after.ToString();
}

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.

Append data to a .csv File using C#

The following code writes the data and is working fine, but I want to add more than one client (maybe 10) in the .csv file. How can I achieve this. Thanks in advance.
private void createFileButton_Click(object sender, EventArgs e)
{
string newFileName = "C:\\client_20100913.csv";
string clientDetails = clientNameTextBox.Text + "," + mIDTextBox.Text + "," + billToTextBox.Text;
//Header of the .csv File
string clientHeader = "Client Name(ie. Billto_desc)" + "," + "Mid_id,billing number(ie billto_id)" + "," + "business unit id" + Environment.NewLine;
File.WriteAllText(newFileName, clientHeader);
File.AppendAllText(newFileName, clientDetails);
MessageBox.Show("Client Added", "Added", MessageBoxButtons.OK);
}
If you want to append the client information to an existing file, how about:
string newFileName = "C:\\client_20100913.csv";
string clientDetails = clientNameTextBox.Text + "," + mIDTextBox.Text + "," + billToTextBox.Text;
if (!File.Exists(newFileName))
{
string clientHeader = "Client Name(ie. Billto_desc)" + "," + "Mid_id,billing number(ie billto_id)" + "," + "business unit id" + Environment.NewLine;
File.WriteAllText(newFileName, clientHeader);
}
File.AppendAllText(newFileName, clientDetails);
This way the header line is only written the first time, when the file is created.
Although it would probably be even nicer to provide a list-detail view that lets you view all clients, add and remove clients, select a client to edit details, and save the complete file with all clients.
It looks to me like you want a new client to be added every time you click the button.
If that's the case, the reason why it doesn't work currently is that the file is being cleared by the line
File.WriteAllText(newFileName, clientHeader);
The simplest change would be to check if the file exists before writing over it:
if (!File.Exists(newFileName))
{
//Header of the .csv File
string clientHeader = "Client Name(ie. Billto_desc)" + "," + "Mid_id,billing number(ie billto_id)" + "," + "business unit id" + Environment.NewLine;
File.WriteAllText(newFileName, clientHeader);
}
Although you could use other strategies, such as creating the file on startup of the application and keeping it open (using something like a StreamWriter). You would then close the writer when your application exited. This would pretty much guarantee that the file couldn't be messed with while your application is open.
You might want to do this because there is a race condition in that code - after you check the file exists, and before you write to the file, a user could delete it. Keeping the file open helps to avoid this, but you may or may not want to do it.
The underlying problem here seems to be where you're getting the data from to append to your CSV file. Your example code looks like it gets the various pieces of data from text boxes on the page, so if you want multiple clients, are they all going to have their data on the screen in text boxes? My instinct is probably not.
It sounds to me like you should be handling this client data using a class of some sort (perhaps persisted in a database) and then implement a method in the class called something like void AppendToCSV(string filename), which appends that client data to the CSV file. Then you can loop over your client objects, appending each one in turn.
How you produce/store your client objects, in relation to the text boxes you have on the screen, depends on what your app is trying to achieve.
I know this has been answered but there is what i did to create a "log" of subscribers. This uses reflection to get the properties and values of the object. Hope this helps someone in the future.
internal static bool UpdateSubscriberList(MailingListEmail subscriber)
{
PropertyInfo[] propertyinfo;
propertyinfo = typeof(MailingListEmail).GetProperties();
var values = string.Empty;
try
{
string fileName = #"C:\Development\test.csv";
if (!File.Exists(fileName))
{
var header = string.Empty;
foreach (var prop in propertyinfo)
{
if (string.IsNullOrEmpty(header))
header += prop.Name;
else
header = string.Format("{0},{1}", header, prop.Name);
}
header = string.Format("{0},{1}", header, "CreatedDate");
header += Environment.NewLine;
File.WriteAllText(fileName, header);
}
foreach (var prop in propertyinfo)
{
var value = prop.GetValue(subscriber, null);
if (string.IsNullOrEmpty(values))
values += value;
else
values = string.Format("{0},{1}", values, value);
}
values = string.Format("{0},{1}", values, DateTime.Now.ToShortDateString());
values += Environment.NewLine;
File.AppendAllText(fileName, values);
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
return false;
}
return true;
}
here is what i have done, and it works for me perfectly :
first you need to creat DataTable from your listview, or just put data from textboxes:
`public Boolean PreparCVS(string DateOne, string DataTwo)
{
try
{
// Create the `DataTable` structure according to your data source
DataTable table = new DataTable();
table.Columns.Add("HeaderOne", typeof(string));
table.Columns.Add("HeaderTwo", typeof(String));
// Iterate through data source object and fill the table
table.Rows.Add(HeaderOne, HeaderTwo);
//Creat CSV File
CreateCSVFile(table, sCsvFilePath);
return true;
}
catch (Exception ex)
{
throw new System.Exception(ex.Message);
}
}`
once dataTable is created you can generate CSV file by this method :
in the streamwriter constructor you must specify in the second parameter True, by this, you can append data to you existing .csv file :
public void CreateCSVFile(DataTable dt, string strFilePath)
{
StreamWriter sw = new StreamWriter(strFilePath, true);
int iColCount = dt.Columns.Count;
for (int i = 0; i < iColCount; i++)
{
sw.Write(dt.Columns[i]);
if (i < iColCount - 1)
{
sw.Write(",");
}
}
sw.Write(sw.NewLine);
foreach (DataRow dr in dt.Rows)
{
for (int i = 0; i < iColCount; i++)
{
if (!Convert.IsDBNull(dr[i]))
{
sw.Write(dr[i].ToString());
}
if (i < iColCount - 1)
{
sw.Write(",");
}
}
sw.Write(sw.NewLine);
}
sw.Close();
}
// At first read all the data from your first CSV
StreamReader oStreamReader = new StreamReader(#"d:\test\SourceFile.csv");
string recordsFromFirstFile = oStreamReader.ReadToEnd();
oStreamReader.Close();
// Now read the new records from your another csv file
oStreamReader = new StreamReader(#"d:\test\DestinationFile.csv");
string recordsFromSecondFile = oStreamReader.ReadToEnd();
oStreamReader.Close();
oStreamReader.Dispose();
// Here Records from second file will also contain column headers so we need to truncate them using Substring() method
recordsFromSecondFile = recordsFromSecondFile.Substring(recordsFromSecondFile.IndexOf('\n') + 1);
// Now merge the records either in SourceFile.csv or in Targetfile.csv or as per your required file
StreamWriter oStreamWriter= new StreamWriter(#"d:\testdata\TargetFile.csv");
oStreamWriter.Write(recordsFromFirstFile + recordsFromSecondFile);
oStreamWriter.Close();
oStreamWriter.Dispose();
Happy Coding.....
c#csv
using CsvHelper;
public void WriteDataToCsv(MsgEnvironmentData[] data, string csvPath)
{
if (!File.Exists(csvPath))
{
using (var writer = new StreamWriter(csvPath))
using (var csvWriter = new CsvWriter(writer,firstConfiguration))
{
csvWriter.WriteHeader<MsgEnvironmentData>();
csvWriter.NextRecord();
csvWriter.WriteRecords(data);
}
}
else
{
using (var stream = new FileStream(csvPath, FileMode.Append))
using (var writer = new StreamWriter(stream))
using (var csvWriter = new CsvWriter(writer, secondConfiguration))
{
csvWriter.WriteRecords(data);
}
}
}
Jeramy's answer writing the contents on last cell and from their horizontally in a row in csv file. I mixed and matched his solution with answer given here. I know this questions been asked long before but for the ones who doing research I'm posting the answer here.
string newFileName = #"C:\.NET\test.csv"; //filepath
var csv = new StringBuilder();
string clientDetails = "content1,content2,content3" + Environment.NewLine;
csv.Append(clientDetails);
File.AppendAllText(newFileName, csv.ToString());
I use this simple piece of code to append data to an existing CSV file:
string[] data = { "John", "Doe", "25" };
string csvFilePath = "example.csv";
// Open the file for writing
using (StreamWriter writer = File.AppendText(csvFilePath))
{
// Write the data row
writer.WriteLine(string.Join(",", data));
}

Categories

Resources