I have a project using CsvHelper that I wrote in VS2017 back in 2017, and was working successfully back then. This week I am trying to drag the code into VS2022, .Net Core, and the latest CsvHelper (33.0.1). In doing so I have discovered a weird failure in CsvHelper.
I am basically using the Get Class Records functionality, and have a class that covers the records in my CSV data, and matches the case of each header etc (and this was working in 2017). For example:
public class Foo
{
public int MyInt { get; set; }
public string MyString { get; set; }
public ComputedClass CustomData {get; set; }
...
}
Where "CustomData" is a class that I compute from some of the fields in "Foo", so is not included in the raw CSV data.
I also have a CsvHelper mapping class for Foo where I tell CsvHelper to ignore the field "CustomData" (this is new in my VS2022 code, and I did it to try and eliminate the issue I am having)
public sealed class FooMap : ClassMap<Foo>
{
public FooMap()
{
AutoMap(CultureInfo.InvariantCulture);
Map(m => m.CustomData).Ignore();
}
}
The matching CSV data being (but no "CustomData" fieldname in the raw CSV file, and this is the same data I was testing in 2017)
MyInt,MyString,...
1,"One",...
2,"Two",...
And I am feeding this configuration to the CsvHelper, and calling the reader like so (where "reader" is a text file reader that has opened the CSV file.
CsvConfiguration config = new(CultureInfo.InvariantCulture)
{
HasHeaderRecord = true,
TrimOptions = TrimOptions.Trim,
MissingFieldFound = null
};
using var csv = new CsvReader(reader, config);
csv.Context.RegisterClassMap<FooMap>();
while (csv.Read())
{
var row = csv.GetRecord<Foo>();
row.CustomData = new ComputedClass(row.MyInt,row.MyString);
...
}
And this is where the fun begins. When executing this code, GetRecord() throws an exception related to the headers, and the exception message starts off with:
Header with name 'myIntParam'[0] was not found.
Header with name 'myStringParam'[0] was not found.
...
The fields that are reported missing do not have the same case as the fields that I defined in the Foo class, nor are they what is in the actual CSV file. I did a full case sensitive search of my solution and I do have "myIntParam" and "myStringParam" as symbols, but they are only used as parameters to the "ComputedClass" constructor.
public ComputedClass(int myIntParam, string myStringParam)
{
...
}
So it seems that even though I am telling CsvHelper to ignore the CustomData property, CsvHelper is still finding the parameters to the constructor for that property. But because they are parameters to the constructor, they can't be added to the FooMap class to be ignored.
How do I eliminate this exception?
Based on @Mason suggesting this answer to another CsvHelper question, I started thinking about how reflection being used by CsvHelper was analyzing Foo in order to produce a list of headers that were expected to be in the CSV data.
For whatever reason it seems that CsvHelper was being a bit overzealous and assuming that the parameters to ComputedClass's constructor were meant to be headers. My first attempt at fixing this was to remove parameters from the constructor and add a "Set()" function which took the same parameters. This worked.
But then I realized that in my code I had another class that was not causing issues even though it too had a parameterized constructor. In looking at this class I saw that I had defined an explicit default constructor alongside the parameterized one.
I applied this idea to ComputedClass resulting in code that does not cause the issue:
// This satisfies CsvHelper when constructing the list of headers
[Obsolete()]
public ComputedClass()
{
}
// This is what I use in code, and CsvHelper now ignores
public ComputedClass(int myIntParam, string myStringParam)
{
...
}
In addition, I removed the FooMap and RegisterClassMap references, and the code continued to work.
I was just looking for ways to mark the constructor as "Do not use", when I encountered another SO question that was asking a similar thing about XML (de)serializers.