I'm using Josh Close's excellent CsvHelper library to read csv files and load them into a database using entity framework. This all works well except for one thing; CsvReader stores an empty string in the csv file as an empty string in the database and I would like this to be a NULL value instead. So what I've done is create a custom converter that takes care of this:
public class NullStringConverter : StringConverter
{
public override object ConvertFromString(TypeConverterOptions options, string text)
{
if (string.IsNullOrEmpty(text))
return null;
else
return base.ConvertFromString(options, text);
}
}
I can apply this to a string property by using a either fluent map syntax or via an attribute and it will now insert a NULL instead of an empty string.
Since I have quite a few classes containing a number of string attributes I would like to avoid having to create Map statements for each and everyone of them. I created a generic Map class that enumerates all properties and applies the custom converter to all string properties. Here's what I have sofar
public class DefaultStringMap<TEntity> : CsvClassMap<TEntity> where TEntity : AbstractAmtSourceEntity
{
public DefaultStringMap()
{
typeof(TEntity).GetProperties()
.Where(p => p.PropertyType == typeof(string))
.ToList()
.ForEach(p => Map(m => p.Name).TypeConverter<NullStringConverter>());
}
}
Here AbstractAmtSourceEntity
is my base class for all my entity classes. Here's my reader class that actually gets the data:
public static void Read<TEntity>(TextReader reader, AmtSourceModel context) where TEntity : AbstractAmtSourceEntity
{
using (var csvReader = new CsvReader(reader))
{
csvReader.Configuration.WillThrowOnMissingField = false;
csvReader.Configuration.Delimiter = "|";
csvReader.Configuration.SkipEmptyRecords = true;
csvReader.Configuration.RegisterClassMap<DefaultStringMap<Entity1>>();
csvReader.Configuration.RegisterClassMap<DefaultStringMap<Entity2>>();
etc...
csvReader.Configuration.IgnoreReadingExceptions = true;
csvReader.Configuration.ReadingExceptionCallback = (ex, row) =>
{
_log.Warn($"Exception caught reading row {row}", ex);
_log.Debug($"Exception detail: {ex.Data["CsvHelper"]}");
};
var records = csvReader.GetRecords<TEntity>();
context.Set<TEntity>().AddRange(records);
context.SaveChanges();
}
}
This doesn't work however, the mapping is not applied, so obviously I'm missing something. Can anyone tell me what's missing here?
You can set converters globally.
TypeConverterFactory.AddConverter( typeof( string ), new NullStringConverter() );
// or
TypeConverterFactory.AddConverter<string>( new NullStringConverter() );