ClassMap.AutoMap requires parameters, but does not allow these to be populated from derived class mappers
Is your feature request related to a problem? Please describe. I have the following class and map defined:
public abstract class CsvBaseModel
{
public long LineNumber { get; set; }
}
public class CsvBaseModelClassMap<TCsvBaseModel> : ClassMap<TCsvBaseModel>
where TCsvBaseModel : CsvBaseModel
{
public CsvBaseModelClassMap()
{
AutoMap(CultureInfo.InvariantCulture); // problem here
Map(m => m.LineNumber).Convert(record => record.Row.Parser.RawRow);
}
}
which I'm registering as follows:
using (var stream = ...)
using (var reader = new StreamReader(stream))
using (var csv = new CsvReader(reader, culture))
{
csv.Context.RegisterClassMap<CsvBaseModelClassMap<DerivedFromCsvBaseModel>>();
... do reading here...
}
Essentially I'm using the ClassMap.AutoMap() method to convention-based map all the properties introduced by the derived classes, as well as add the line number.
The problem is that AutoMap() has 3 overloads, all of which have mandatory parameter(s), but CsvContext.RegisterClassMap() doesn't allow those parameters to be specified, so there's no way to control what is passed to AutoMap(). Based on my testing, it does appear that whatever is passed to these overloads is ignored in favour of the options specified for the CsvReader when constructing it, but even so this is confusing.
Describe the solution you'd like
- Add a no-args overload of
ClassMap.AutoMap()that delegates to the options specified when constructing the wrapping reader/writer. - Add overloads of
CsvContext.RegisterClassMap()that correspond to the current overloads ofClassMap.AutoMap(), accept mappers with constructors that match those overloads, and make the mapper registration invoke the appropriate constructor. For example:
public class CsvBaseModelClassMap<TCsvBaseModel> : ClassMap<TCsvBaseModel>
where TCsvBaseModel : CsvBaseModel
{
public CsvBaseModelClassMap(CultureInfo cultureInfo)
{
AutoMap(cultureInfo);
Map(m => m.LineNumber).Convert(record => record.Row.Parser.RawRow);
}
}
...
using (var stream = ...)
using (var reader = new StreamReader(stream))
using (var csv = new CsvReader(reader, culture))
{
// note that this version of RegisterClassMap and the CsvBaseModelClassMap constructor both accept a CultureInfo.
// when the actual reading and mapping take place, this specific CultureInfo provided to RegisterClassMap will be
// passed into CsvBaseModelClassMap's constructor and used, instead of the one specified in the CsvReader constructor.
csv.Context.RegisterClassMap<CsvBaseModelClassMap<DerivedFromCsvBaseModel>>(
CultureInfo.GetCultureInfo("whatever-WHATEVER")
);
... do reading here...
}
RegisterClassMap has an overload that takes in a map, so you can do this:
var map = new CsvBaseModelClassMap<DerivedFromCsvBaseModel>(CultureInfo.GetCultureInfo("whatever-WHATEVER"));
csv.Context.RegisterClassMap(map);
https://github.com/JoshClose/CsvHelper/blob/808dea2456b9c695eed1c124f67a2385e88b8a81/src/CsvHelper/CsvContext.cs#L136
Hrm, that kinda works, except I don't really want to have to manually instantiate the ClassMap. I much prefer convention-based mapping, hence this proposal... perhaps I should reword it.
Is there a reason you can't call new? Like do you only know the type at runtime?