Writing a Header using CsvHelper? C#

Smithy picture Smithy · Jun 29, 2016 · Viewed 24.2k times · Source

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.

Answer

Ňɏssa P&#248;ngjǣrdenlarp picture Ňɏssa Pøngjǣrdenlarp · Jun 29, 2016

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.