I'm a newbie. I want to get data from the CSV file-the Id and Name fields, but when I run the reading method, I get only 100 lines of an incomprehensible type: "CsvHelper.CsvReaderd__87`1[Program+Product]". I do not know how to get data from CSV, I also cannot understand where the error is.
Although the documentation says that having the same names of properties and CSV headers, you do not need to write additional configurations. However, I get the result specified above. The CSV names match the classes. Link to the documentation:https://joshclose.github.io/CsvHelper/getting-started/
reading method:
{
using (var reader = new StreamReader("C:\\Users\\Saint\\Desktop\\TaskRetail\\file.csv", Encoding.UTF8))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<Product>();
Console.WriteLine($"{records}");
}
}
CSV is created without problems, there are two columns with Id and Name with filled rows, there are 100 rows in total:
method for creating a csv with the Id and Name fields:
using (var writer = new StreamWriter("C:\\Users\\Saint\\Desktop\\TaskRetail\\file.csv", false, Encoding.UTF8))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(products);
}
the entire code:
using CsvHelper;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
public class Program
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public Product(int id, string name)
{
Id = id;
Name = name;
}
}
public const string PathToDoc = "C:/Users/Saint/Desktop/TaskRetail/yml.xml";
public static void Main(string[] args)
{
string url = "https://www.googleapis.com/drive/v3/files/1sSR9kWifwjIP5qFWcyxGCxN0-MoEd_oo?alt=media&key=AIzaSyBsW_sj1GCItGBK0vl8hr9zu1I1vTI1Meo";
string savePath = #"C:\Users\Saint\Desktop\TaskRetail\yml.xml";
WebClient client = new WebClient();
client.DownloadFile(url, savePath);
Research();
}
public static void Research()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var document = new XmlDocument();
document.Load(PathToDoc);
var xmlDoc = document.SelectNodes("/yml_catalog/shop/offers/offer");
var count = xmlDoc.Count;
var products = new List<Product>();
Console.WriteLine($"Offers count: {count}");
for (var i = 0; i < count; i++)
{
var element = xmlDoc.Item(i);
var id = int.Parse(element.Attributes.GetNamedItem("id").Value);
var name = element.SelectSingleNode("name").InnerText;
var product = new Product(id, name);
//Console.WriteLine($"Id: {id}, name: {name}");
products.Add(product);
using (var writer = new StreamWriter("C:\\Users\\Saint\\Desktop\\TaskRetail\\file.csv", false, Encoding.UTF8))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
csv.WriteRecords(products);
}
var config = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ",", PrepareHeaderForMatch = header => header.Header.ToLower() };
using (var reader = new StreamReader("C:\\Users\\Saint\\Desktop\\TaskRetail\\file.csv", Encoding.UTF8))
using (var csv = new CsvReader(reader, config))
{
var records = csv.GetRecords<Product>();
foreach (var record in records)
{
Console.WriteLine($"{record.Id} {record.Name}");
}
}
}
}
}
Because GetRecords() does return an object of type IEnumerable,
you have to iterate over your records to print each one of them:
foreach(var record in records)
{
Console.WriteLine($"{record.Id} {record.Name}");
}
Furthermore you have to access each property you want to print individually.
Another option would be to override the ToString() method in your Product class.
EDIT
The initial problem wasn't the correct printing of the values but the parsing of the file as I learned from this comment:
CsvHelper.HeaderValidationException: 'Header with name 'id'[0] was not found. Header with name 'name'[0] was not found.
To tackle this problem one have to make sure that the delimiter character is set correctly. This can be enforced in the config object of the CsvHelper. Furthermore to avoid casing errors the configuration can be set to ignore the casing of the headers:
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ",", // Enforce ',' as delimiter
PrepareHeaderForMatch = header => header.Header.ToLower() // Ignore casing
};
using (var csv = new CsvReader(reader, config))
{
...
}
Related
The code below reads a column of a csv file.
It does that properly.
I want to copy var records to a decimal array.
I am using csv helper.
How best to do it?
using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = new List<Filevalues>();
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var record = new Filevalues
{
File_vals = csv.GetField<decimal>("File_vals"),
};
records.Add(record);
}
}
public class Filevalues
{
public decimal File_vals{ get; set; }
}
At its most simple:
using var csv = new CsvReader(new StreamReader(filename), CultureInfo.InvariantCulture));
csv.GetRecords<Filevalues>().Select(f => f.File_vals).ToArray();
(I think I'd ToList() it instead of array, and work with the List)
If the CSV is so simple that it's just a list of decimals, I might skip using a CSV library all together:
File.ReadLines(path).Skip(1).Select(decimal.Parse).ToArray();
Either:
using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var listOfDecimals = new List<decimal>();
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
listOfDecimals.Add(csv.GetField<decimal>("File_vals"));
}
var arrayOfDecimals = listOfDecimals.ToArray();
}
ToArray: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.toarray
Or:
using (var reader = new StreamReader(filename))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = new List<Filevalues>();
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var record = new Filevalues
{
File_vals = csv.GetField<decimal>("File_vals"),
};
records.Add(record);
}
var arrayOfDecimals = records.Select(x => x.File_vals).ToArray();
}
public class Filevalues
{
public decimal File_vals{ get; set; }
}
LINQ Projection: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/projection-operations
I have a csv file that contains integer, string, bool, and float types of data. The problem is, that some fields are empty. When I try to read the csv file with csvhelper, I get TypeConversion.TypeConverterException error. I checked the details, and looks like it's because it can't convert the empty float values to string. How can I handle it?
namespace Smh
{
class Program
{
static void Main(string[] args)
{
List<E> EList = new List<E>();
List<E2> EList2 = new List<E2>();
string filePath = #"D:\Visual Studio\Projects\Example\boo.csv";
using (var reader = new StreamReader(filePath))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var example= new E();
EList= csv.GetRecords<E>().ToList();
}
.
.
.
A part of my "E" class:
.
.
public float weight { get; set; }
.
.
and the error I get:
https://i.postimg.cc/dVPkYDCm/image.png
A short part of the csv file:
id | speed | type | weight
1 40 type1 5.4
2 43 type2
3 16 type3 5.2
The simplest solution with out using a ClassMap Custom mapper,
is to change the type to float?.
public class Test{
public int id{get;set;}
public int speed{get;set;}
public string type{get;set;}
public float? weight{get;set;}
}
Then If you need to change the default value you can add a Getter Setter that will return Single.NaN
Example:
Live Demo
public static void Main()
{
var result = new List<Test>();
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
using (var reader = new StreamReader(stream))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
//Csv Creation
writer.WriteLine("id,speed,type,weight");
writer.WriteLine("1,40,type1,5.4");
writer.WriteLine("2,43,type2,");
writer.WriteLine("3,16,type3,5.2");
writer.Flush();
stream.Position = 0;
csv.Configuration.HasHeaderRecord = true;
csv.Configuration.Delimiter = ",";
result = csv.GetRecords<Test>().ToList();
}
result.Dump();
}
I need to manipulate an existing CSV file via following actions:
Read from an existing CSV file -> then Append new row to it.
I have following code which is choking over the 3rd row - as the file is already in use by the code from the 1st row. And I'm not sure how to read it properly otherwise, and then append new row to it.
public bool Save(Customer customer)
{
using (StreamReader input = File.OpenText("DataStoreOut.csv"))
using (CsvReader csvReader = new CsvReader(input))
using (StreamWriter output = File.CreateText("DataStoreOut.csv"))
using (var csvWriter = new CsvWriter(output))
{
IEnumerable<Customer> records = csvReader.GetRecords<Customer>();
List<Customer> customerList = new List<Customer>();
customerList.Add(customer);
csvWriter.WriteHeader<Customer>();
csvWriter.NextRecord();
foreach (var array in customerList)
{
csvWriter.WriteRecord(records.Append(array));
}
}
}
Each of row in the CSV file contains a customer.CustomerId (which is unique, and read-only). How can I read only row which has specific customerId and then update any values there.
If you want to append a record to a file, the best way to do it is read the items, add the new one to the collection, and write everything back.
public static void Append(Customer customer, string file)
{
List<Customer> records = null;
using (var reader = new StreamReader(file))
{
using (var csv = new CsvReader(reader))
{
records = csv.GetRecords<Customer>().ToList();
}
}
records.Add(customer);
using (var writer = new StreamWriter(file))
{
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}
}
}
As #Dour High Arch mentioned, to be perfectly safe though you might want to take the extra step of using a temp file in case something goes wrong.
If you want to update instead of append, you'd have to look up the specified record, and update it if it exists.
public static void Update(Customer customer, string file)
{
List<Customer> records = null;
using (var reader = new StreamReader(file))
{
using (var csv = new CsvReader(reader))
{
records = csv.GetRecords<Customer>().ToList();
}
}
var index = records.FindIndex(x => x.ID == customer.ID);
if (index >= 0)
{
records[index] = customer;
using (var writer = new StreamWriter(file))
{
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}
}
}
}
Again, writing to a temp file is advisable.
UPDATE
Actually there's a slightly better way to append if you don't want to replace the file. When instantiating a StreamWriter you can do so with append=true. In which case, it will append to the end of the file.
The small caveat is that in case the EOF marker is not at a new line but at the last field of the last record, this will append record to the end of the last field messing up your columns. As a workaround I've added a writer.WriteLine(); before using the CSVHelper class' writer.
public static void Append2(Customer customer, string file)
{
using (var writer = new StreamWriter(file, true))
{
writer.WriteLine();
using (var csv = new CsvWriter(writer))
{
csv.WriteRecord(customer);
}
}
}
In case the file is in a new line, then this will add an empty line though. That can be countered by ignoring empty lines when you read a file.
I am writing my data from a public class to a CSV file. As I want to append my data, I want to exclude the importing of header and only import the data from the class. My code below imports both headers and data. Hope to get help. Thanks.
Record.cs - my class
public class Record
{
public string Name
{
get; set;
}
public DateTime DateOfBirth
{
get; set;
}
}
Form1.cs - my form
public partial class Form1 : Form
{
private List<Record> records;
public Form1()
{
InitializeComponent();
records = new List<Record>();
}
private void Savetocsv_Click(object sender, EventArgs e)
{
var myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var writer = new StreamWriter(myDocument + "/my-data.csv", append: true))
{
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}
}
}
Using the Configuration , you can use the property HasHeaderRecord:
HasHeaderRecord :
Gets or sets a value indicating if the CSV file has a header record.
Default is true.
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 1, Name = "one" },
};
using (var writer = new StreamWriter($"file.csv"))
using (var csv = new CsvWriter(writer, new Configuration { HasHeaderRecord = false }))
{
csv.WriteRecords(records);
}
Result file : "file.csv"
1;one
1;one
Or simply loop on records an write them:
var records = new List<Foo>
{
new Foo { Id = 1, Name = "one" },
new Foo { Id = 1, Name = "one" }
};
using (var writer = new StreamWriter($"file.csv"))
using (var csv = new CsvWriter(writer))
{
foreach (var record in records)
{
csv.WriteRecord(record);
csv.NextRecord();
}
}
The name of the configuration class has changed.
using (var csv = new CsvWriter(outputStream, new CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false
}))
Change your writing method as following and then CsvHelper.WriterConfiguration do the trick (note HasHeaderRecord):
private void Savetocsv_Click(object sender, EventArgs e)
{
var myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var writer = new StreamWriter(myDocument + "/my-data.csv", append: true))
{
using (var csv = new CsvWriter(writer, new Configuration { HasHeaderRecord = false }))
{
csv.WriteRecords(records);
}
}
}
I don't know which CsvWriter you are using, but the one here has a HasHeaderRecord property that you can use to ignore or include headers.
private void Savetocsv_Click(object sender, EventArgs e)
{
var myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var writer = new StreamWriter(myDocument + "/my-data.csv", append: true))
{
using (var csv = new CsvWriter(writer))
{
csv.Configuration.HasHeaderRecord = true;
csv.WriteRecords(records);
}
}
}
Remove the first row from records before calling:
csv.WriteRecords(records);
(If you need to leave records unchanged, add the headers back again after calling WriteRecords(...).)
private void Savetocsv_Click(object sender, EventArgs e)
{
var myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var writer = new StreamWriter(myDocument + "/my-data.csv", append: true))
{
using (var csv = new CsvWriter(writer))
{
records.RemoveAt(0); // Removes the header row.
csv.WriteRecords(records);
}
}
}
I tried to write to CSV file using CsvHelper in C#.
This is the link to the library http://joshclose.github.io/CsvHelper/
Nothing is sent to the csv file. I tried doing "exportCsv.WriteField("Hello");" but still nothing happened.
List<string> ColumnOne = new List<string>();
List<string> ColumnTwo = new List<string>();
var csvTextWriter = new
StreamWriter(#"C:\Users\Public\Documents\ExportTest.csv");
var exportCsv = new CsvWriter(csvTextWriter);
//creating a list to store workflows then adding name and description to the myWorkflowsList list
if (myWorkflows.WorkFlowCollection.Any())
{
foreach (var Workflow in myWorkflows.WorkFlowCollection)
{
ColumnOne.Add(Workflow.WorkflowName);
ColumnTwo.Add(Workflow.WorkflowDescription);
}
exportCsv.WriteField(ColumnOne);
//exportCsv.WriteField(ColumnTwo);
exportCsv.NextRecord();
exportCsv.Flush();
Console.WriteLine("File is saved:
C:\\Users\\Public\\Documents\\ExportTest.csv");
Console.ReadLine();
}
Your code doesn't add any records. It doesn't have any calls to WriteRecords or WriteRecord. It looks like it's trying to write an entire list of strings into a single field instead.
To write two columns out to a file you can use `WriteRecords, eg :
var data = from flow in myWorkflows.WorkFlowCollection
select new { flow.WorkflowName,flow.WorkflowDescription};
using (var writer = new StreamWriter("test.csv"))
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(data);
}
This will write a file with field names WorkflowName and WorkflowDescription
You can change how the fields are written by creating a small class that accepts only the fields you want and sets names etc through attributes :
class Flow
{
[NameAttribute("Workflow Name")]
public string WorkflowName { get; set; }
[NameAttribute("Workflow Description")]
public string WorkflowDescription { get; set; }
public Flow(string workflowName, string workflowDescription)
{
WorkflowName = workflowName;
WorkflowDescription = workflowDescription;
}
}
//...
var data = from flow in myWorkflows.WorkFlowCollection
select new Flow(flow.WorkflowName,flow.WorkflowDescription);
using (var writer = new StreamWriter("test.csv"))
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(data);
}