I just found a few answers for this, but found them all horribly long with lots of iterations, so I came up with my own solution:
Convert table to string:
string myTableAsString =
String.Join(Environment.NewLine, myDataTable.Rows.Cast<DataRow>().
Select(r => r.ItemArray).ToArray().
Select(x => String.Join("\t", x.Cast<string>())));
Then simply save string to text file, for example:
StreamWriter myFile = new StreamWriter("fileName.txt");
myFile.WriteLine(myFile);
myFile.Close();
Is there a shorter / better way?
You have your DataTable named as myDataTable, you can add it to DataSet as:
var dataSet = new DataSet();
dataSet.AddTable(myDataTable);
// Write dataset to xml file or stream
dataSet.WriteXml("filename.xml");
And you can also read from xml file or stream:
dataSet.ReadXml("filename.xml");
#Leonardo sorry but i can 't comment so i post.
Sometimes you can ask the dataset and then work with it. Like this:
foreach (DataRow row in ds.Tables[0].Rows)
{
foreach (object item in row.ItemArray)
{
myStreamWriter.Write((string)item + "\t");
}
myStreamWriter.WriteLine();
}
That 's another way but i don 't know which 'll give you a better metric.
If you consider XML as text you can do: myDatatable.WriteXml("mydata.xml") and myDatatable.ReadXml("mydata.xml")
You get an error unless you save it with the schema:
myDataTable.WriteXml("myXmlPath.xml", XmlWriteMode.WriteSchema);
myDatatable.ReadXml("myXmlPath.xml");
There is more info on saving/loading with schema here:
DataTable does not support schema inference from Xml.?
Related
Hi i have this code To export a List to An Excel:
private DataTable ListaDatiReportQuietanzamento(List<DatiReportQuietanzamento> datiReportQuietanzamento)
{
DataTable dt = new DataTable("DatiReportQuietanzamento");
dt.Columns.Add("Polizza");
dt.Columns.Add("Posizione");
dt.Columns.Add("Codice Frazionamento");
var result = datiReportQuietanzamento.ToDataTable().AsEnumerable().Select(p =>
new
{
n_polizza = p.Field<long>("n_polizza"),
n_posizione = p.Field<byte>("n_posizione"),
c_frazionamento = p.Field<string>("c_frazionamento")
}).Distinct().ToList();
foreach (var item in result)
{
dt.Rows.Add(item.n_polizza, item.n_posizione, item.c_frazionamento);
}
return dt;
}
This method works with Lists that does not contain many items , but when the list is very large , the method takes too many time.
There is a way to avoid the foreach and add to the rows the items directly? Maybe with Lambda Expression?
Thank you.
While you have not specified how the data is ultimately to be supplied to Excel, generally it is supplied a CSV (Comma Separated Values) file for easy import.
So this being the case you can eliminate your data table conversion entirely and create a list of strings as follows:
private List<string> ListaDatiReportQuietanzamento(List<DatiReportQuietanzamento> datiReportQuietanzamento)
{
var result = new List<string>();
foreach (var item in datiReportQuietanzamento)
{
result.AppendLine($"{item.n_polizza},{item.n_posizione},{item.c_frazionamento}");
}
return result;
}
Now the only simplification I have made is not to worry about encoding because strings should actually be escaped so item.c_frazionamento should actually be escaped.
Instead of doing this all yourself, I suggest you have a look at a NuGet package such as CsvHelper which will help you with creating CSV files and take all the hassle with escaping things out of the equation. It can also directly deal with a list of objects and convert it into a CSV file for you see specifically the first example in https://joshclose.github.io/CsvHelper/writing#writing-all-records
I've searched around for a solution to this question but can't find an applicable circumstance and can't get my head around it either.
I've got a List<String[]> object (a parsed CSV file) and want to remove any rows if the first value in the row is equal to my criteria.
I've tried the following (with variations) and can't seem to get it to delete the lines, it just passes over them:
rows.RemoveAll(s => s[0].ToString() != "Test");
Which I'm currently reading as, remove s if s[0] (the first value in the row) does not equal "Test".
Can someone point me in the right direction for this?
Thanks, Al.
Edit for wider context / better understanding:
The code is as follows:
private void CleanUpCSV(string path)
{
List<string[]> rows = File.ReadAllLines(path).Select(x => x.Split(',')).ToList();
rows.RemoveAll(s => s[0] != "Test");
using (StreamWriter writer = new StreamWriter(path, false))
{
foreach (var row in rows)
{
writer.WriteLine(row);
}
}
}
So the question is -> Why won't this remove the lines that do not start with "Test" and upon writing, why is it returning System.String[] as all the values?
Did you try with Where? Where is going to filter based on a predicate. You should be able to do something like this:
Demo: Try it online!
List<string[]> rows = new List<string[]> { new []{"Test"}, new []{ "Foo"} };
rows = rows.Where(s => s[0] == "Test").ToList();
foreach(var row in rows)
{
Console.WriteLine(string.Join(",", row));
}
output
Test
You dont need ToString() because S[0] is already a string
You may want to handle empty case or s[0] could throw
You can use s.First() instead of s[0]
You can learn more about Predicateon msdn
Edit
For your example:
private void CleanUpCSV(string path)
{
var rows = File.ReadAllLines(path).Select(x => x.Split(','));
using (StreamWriter writer = new StreamWriter(path, false))
{
foreach (var row in rows.Where(s => s[0] == "Test"))
{
writer.WriteLine(string.Join(",", row));
}
}
}
By the way, you may want to use a library to handle csv parsing. I personally use CsvHelper
The only error in your code is the following:
Since row is string[] this
writer.WriteLine(row);
won't give you the result you were expecting.
Change it like this
writer.WriteLine(String.Join(",", row));
To convert the string[]back into its orginal form.
Any other "optimisation" in all the answers proposed here arent really optimal either.
If you're really trying to remove items where the first element isn't "Test", then your code should work, though you don't need to call .ToString() on s[0] since it's already a string. If this doesn't work for you, perhaps your problem lurks elsewhere? If you give an example of your code in a wider context you could get more help
Filter it like this instead:
var filteredList = rows.Where(s => s[0] == "test").ToArray();
I have a csv file I am going to read from disk. I do not know up front how many columns or the names of the columns.
Any thoughts on how I should represent the fields. Ideally I want to say something like,
string Val = DataStructure.GetValue(i,ColumnName).
where i is the ith Row.
Oh just as an aside I will be parsing using the TextFieldParser class
http://msdn.microsoft.com/en-us/library/cakac7e6(v=vs.90).aspx
That sounds as if you would need a DataTable which has a Rows and Columns property.
So you can say:
string Val = table.Rows[i].Field<string>(ColumnName);
A DataTable is a table of in-memory data. It can be used strongly typed (as suggested with the Field method) but actually it stores it's data as objects internally.
You could use this parser to convert the csv to a DataTable.
Edit: I've only just seen that you want to use the TextFieldParser. Here's a possible simple approach to convert a csv to a DataTable:
var table = new DataTable();
using (var parser = new TextFieldParser(File.OpenRead(path)))
{
parser.Delimiters = new[]{","};
parser.HasFieldsEnclosedInQuotes = true;
// load DataColumns from first line
String[] headers = parser.ReadFields();
foreach(var h in headers)
table.Columns.Add(h);
// load all other lines as data '
String[] fields;
while ((fields = parser.ReadFields()) != null)
{
table.Rows.Add().ItemArray = fields;
}
}
If the column names are in the first row read that and store in a Dictionary<string, int> that maps the column name to the column index.
You could then store the remaining rows in a simple structure like List<string[]>.
To get a column for a row you'd do csv[rowIndex][nameToIndex[ColumnName]];
nameToIndex[ColumnName] gets the column index from the name, csv[rowIndex] gets the row (string array) we want.
This could of course be wrapped in a class.
Use the csv parser if you want, but a text parser is something very easy to do by yourself if you need customization.
For you need, i would use one (or more) Dictionnary. At least one to have the PropertyString --> column index. And maybe the reverse one column index--> PropertyString if needed.
When i parse a file for csv, i usually put the result in a list while parsing, and then in an array once complete for speed reasons (List.ToArray()).
I have a DataSet. I would like to convert dataset column as header and row data as data into a tab delimited text file.
Is there any technique I can do in my end or I have to do the looping manually?
Sincerely Thanks,
- Sel
private static string GetTextFromDataTable(DataTable dataTable)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine(string.Join("\t", dataTable.Columns.Cast<DataColumn>().Select(arg => arg.ColumnName)));
foreach (DataRow dataRow in dataTable.Rows)
stringBuilder.AppendLine(string.Join("\t", dataRow.ItemArray.Select(arg => arg.ToString())));
return stringBuilder.ToString();
}
Usage:
var text = GetTextFromDataTable(dataSet.Tables[0]);
File.WriteAllText(filePath, text);
Exporting to XML is built right in, but exporting to CSV, you can use the following code - from http://social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/d2071fd4-8c7d-4d0e-94c3-9586df754df8/
this only writes the data, not the columns, you'll need to loop the column headers first..
Edit: Updated to include column names... I have not run this, and this is an edit from the link above, so it may or may not work, but the concept is here
StringBuilder str = new StringBuilder();
// get the column headers
foreach (var c in NorthwindDataSet.Customers.Columns) {
str.Append("\"" + c.ColumnName.ToString() + "\"\t");
}
str.Append("\r\n");
// write the data here
foreach (DataRow dr in this.NorthwindDataSet.Customers) {
foreach (var field in dr.ItemArray) {
str.Append("\"" + field.ToString() + "\"\t");
}
str.Append("\r\n");
}
try {
My.Computer.FileSystem.WriteAllText("C:\\temp\\testcsv.csv", str.ToString(), false);
} catch (Exception ex) {
MessageBox.Show("Write Error");
}
Note you will need to be using Linq for this solution to work. Add the following using statement to your code:
using System.Linq;
So here's what I'm working with. I'm trying to take an XML file, pull the info from the attributes, append them together, and write it to a CSV file. I'm still relatively new to programming, and the other programmer is out of the office today, so I could really use some assistance.
My first question, regards the StringBuilder. Do I need to have an AppendLine at the end of my StringBuilder, so that each string output from the foreach loop is on a new line? And would I need to do that inside the foreach loop?
My second question regards actually writing my string to the CSV file. Would it look something like?
swOutputFile.WriteLine(strAppendedJobData)
And I think this would also go inside the foreach loop, but I'm not too sure.
Thanks for the help, I hope I've worded my question in a somewhat easy to understand manner.
//Create a stream writer to write the data from returned XML job ticket to a new CSV
StreamWriter swOutputFile;
string strComma = ",";
swOutputFile = new StreamWriter(new FileStream("C:\\Dev\\AppendedJobData.csv", FileMode.Create, FileAccess.Write, FileShare.Read));
//Get nodes from returned XML ticket
XmlNodeList xmlJobs = xdResults.SelectNodes("/Updates/Jobs/Job");
//Pull out data from XML attributes
foreach (XmlElement xeJobUpdate in xmlJobs)
{
//Break down the job data
string strProjectID = xeJobUpdate.GetAttribute("SharpOwlProjectID");
string strJobNumber = xeJobUpdate.GetAttribute("JobNumber");
string strClientCode = xeJobUpdate.GetAttribute("SharpOwlClientCode");
string strClient = xeJobUpdate.GetAttribute("Client");
string strVCAOffice = xeJobUpdate.GetAttribute("VCAOffice");
string strLoadStatus = xeJobUpdate.GetAttribute("LoadStatus");
//Build the string to be added to the new CSV file
StringBuilder sbConcatJob = new StringBuilder();
sbConcatJob.Append(strProjectID).Append(strComma).Append(strJobNumber)
.Append(strComma).Append(strClientCode).Append(strComma).Append(strClient).Append(strComma)
.Append(strVCAOffice).Append(strComma).Append(strLoadStatus).Append(strComma);
string strAppendedJobData = sbConcatJob.ToString();
if you want to do it a bit more elegant you could do something like that:
using(StreamWriter swOutputFile = new StreamWriter(new FileStream("C:\\Dev\\AppendedJobData.csv", FileMode.Create, FileAccess.Write, FileShare.Read)))
{
//Get nodes from returned XML ticket
XmlNodeList xmlJobs = xdResults.SelectNodes("/Updates/Jobs/Job");
//Pull out data from XML attributes
foreach (XmlElement xeJobUpdate in xmlJobs)
{
List<String> lineItems = new List<String>();
lineItems.add(xeJobUpdate.GetAttribute("SharpOwlProjectID"));
//add all the other items
swOutputFile.WriteLine(String.Join(',', myLine.ToArray()));
}
//after the loop you close the writer
}
//all the work is done much easier
My first question, regards the
StringBuilder. Do I need to have an
AppendLine at the end of my
StringBuilder, so that each string
output from the foreach loop is on a
new line? And would I need to do that
inside the foreach loop?
My only advice since it appears you have not attempted this would be to try it. It is the only way you will learn.
swOutputFile.WriteLine(strAppendedJobData)
This would write an entire line of text to a file.
You really have two options here:
If you call sbConcatJob.AppendLine() inside the foreach loop you can build the contents of the file in one string builder then call swOutputFile.Write(sbConcatJob.ToString()) outside of the foreach loop to write the file.
If you keep your code as it is now you can add sw.OutputFile.WriteLine(sbConcatJob.ToString()) inside the foreach loop and write the file one line at a time.