8

I'm using CsvHelper. To write to a .csv file, I need a header based off of a class.

I write the header manually and it works, but I need to be done automatically when it is read.

All the documentation I can find says to use this expression, writer.WriteHeader<CSVDataFormat>(); but that isn't working because it needs more work done to it.

Here is the class that the header should be based off:

public class CSVDataFormat
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public float Wage { get; set; }
}

Here is the code for the reading and writing:

private void ReadCSV(string ogCsvFile)
{
        using (var streamReaederfileDir = new StreamReader(@ogCsvFile))
        {
            using (var streamWriterFileDir = new StreamWriter(Path.Combine(Path.GetDirectoryName(ogCsvFile), "New" + Path.GetFileName(ogCsvFile))))
            {
                var reader = new CsvReader(streamReaederfileDir);
                var writer = new CsvWriter(streamWriterFileDir);

                writer.WriteHeader<CSVDataFormat>();

                IEnumerable records = reader.GetRecords<CSVDataFormat>().ToList();

                foreach (CSVDataFormat record in records)
                {
                    record.Wage = record.Wage + (record.Wage / 10);
                    writer.WriteField(record.FirstName);
                    writer.WriteField(record.LastName);
                    writer.WriteField(record.Wage);
                    writer.NextRecord();
                }
            }
        }
}

Update

This is the error I am getting when I run my code:

An unhandled exception of type 'CsvHelper.CsvMissingFieldException' occurred in CsvHelper.dll

Additional information: Fields 'FirstName' do not exist in the CSV file.

4
  • 1
    What more do you need done in the header? I cant see where you are writing one at all Commented Jun 29, 2016 at 0:29
  • 1
    @Plutonix I updated my code where I use the expression. Commented Jun 29, 2016 at 0:33
  • 1
    @Plutonix The writer.WriteHeader<CSVDataFormat>(); is meant to get the ` get & set ` funtions from the CSVDataFormat class and add their names and data types to the top of the CSV file. That header is required for CsvHelper. Commented Jun 29, 2016 at 0:38
  • 1
    Reading and writing are 2 different operations, so you will have to have a line of code to write the header. The error is something else - the property name doesnt match the csv header names. Can you post the header and maybe one line of data? (WriteHeader is going to write "FirstName, LastName, Wage" not not going to write the values of those props) Commented Jun 29, 2016 at 0:40

2 Answers 2

17

You may be confused how CSVHelper works. This code handles the write aspect of your read in-write out loop:

List<Employee> empList = new List<Employee>();

empList.Add(new Employee { FirstName = "Ziggy", LastName = "Walters", Wage = 132.50F });
empList.Add(new Employee { FirstName = "Zoey", LastName = "Strand", Wage = 76.50F });

using (StreamWriter sw = new StreamWriter(@"C:\Temp\emp.csv"))
using (CsvWriter cw = new CsvWriter(sw))
{
    cw.WriteHeader<Employee>();

    foreach (Employee emp in empList)
    {
        emp.Wage *= 1.1F;
        cw.WriteRecord<Employee>(emp);
    }
}
  • CSVWriter implements IDisposable, so I put it into a using block as well.
  • The wage adjustment is slightly streamlined

Result:

FirstName,LastName,Wage
Ziggy,Walters,145.75
Zoey,Strand,84.15

Write header just writes the first line - the names of the columns/items. Notice that the wages listed are different than what I used to create each one.

For what you are doing, I would read in a typed object in place of iterating the empList. For the error listed in the edit, that means that it could not find a column by that name in the input file (probably because you didnt use the Types overload). The class property names should match the column names exactly (you may also want to configure CSVHelper).


The full in-out loop is only slightly more complex:

using (StreamReader sr = new StreamReader(@"C:\Temp\empIN.csv"))
using (StreamWriter sw = new StreamWriter(@"C:\Temp\empOUT.csv"))
using (CsvWriter cw = new CsvWriter(sw))
using (CsvReader cr = new CsvReader(sr))
{
    cw.WriteHeader<Employee>();
    var records = cr.GetRecords<Employee>();

    foreach (Employee emp in records)
    {
        emp.Wage *= 1.1F;
        cw.WriteRecord<Employee>(emp);
    }

}

Results using the output from the first loop as input:

FirstName,LastName,Wage
Ziggy,Walters,160.325
Zoey,Strand,92.565


If there is no header record in the incoming CSV it wont know how to map data to the class. You need to add a map:

public class EmployeeMap :  CsvHelper.Configuration.CsvClassMap<Employee>
{
    public EmployeeMap()
    {
        Map(m => m.FirstName).Index(0);
        Map(m => m.LastName).Index(1);
        Map(m => m.Wage).Index(2);
    }
}

Mine is nested inside the Employee class. Then give CSVHelper that map:

... before your try to read from the incoming CSV:
cr.Configuration.RegisterClassMap<Employee.EmployeeMap>();

cw.WriteHeader<Employee>();
...

Now it knows how to map csv columns to the properties in your class.

Sign up to request clarification or add additional context in comments.

11 Comments

How can I make empList equal the contents of my csv file?
You kind of already have that in Records. You do need to make sure CSVDataFormat correctly defines your incoming CSV. writer.WriteField<CSVDataFormat>(record);
I can't use IEnumerable records = reader.GetRecords<CSVDataFormat>().ToList(); with the solution you have provided
Why? Is there an error? what is it? var records = reader.GetRecords<CSVDataFormat>() should give you an IEnumerable<CSVDataFormat>` which is what you want. ToList() will force it to read them all into memory - part of the charm of CSVH is loading them one by one as needed.
Additional information: Fields 'FirstName' do not exist in the CSV file. is the error I'm getting.
|
0

I believe this exception is from the CsvReader and not the CsvWriter. Default CsvConfiguration expects a header and uses AutoMap to generate a PropertyName_to_Index mapping.

From the documentation you may need to define a map see mapping

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.