0

I'm importing a csv of sales orders into CsvSalesOrder objects. The CsvSalesOrder has an Address property, and its properties are of a custom type called CustomString. How can I convert the strings into the CustomString types when importing them? My CustomString class:

public struct CustomString
{
    public string Value { get; }

    public CustomString(string val)
    {
        if (string.IsNullOrWhiteSpace(val))
        {
            Value = null;
        }
        else
        {
            Value = val.ToUpper();
        }
    }

    public static implicit operator string(CustomString s) => s.Value;
    public static explicit operator CustomString(string s) => new CustomString(s);
    public override string ToString() => Value;
}

My mapping setup:

var config = new ChoCSVRecordConfiguration<T>()
        .WithFirstLineHeader()
        .Configure(c => c.ThrowAndStopOnMissingField = false)
        .Configure(c => c.IgnoreEmptyLine = true)
        .Configure(c => c.FileHeaderConfiguration.IgnoreColumnsWithEmptyHeader = true);

foreach (var header in headers)
{
    if (mapping.TryGetValue(header, out var propName))
        config.Map(propName, header);
}

My import code:

using var reader = new ChoCSVReader<T>(stream, config)
        .WithMaxScanRows(2)
        .QuoteAllFields()
        .IgnoreFieldValueMode(ChoIgnoreFieldValueMode.Any);
return reader.AsTypedEnumerable<T>();

The Address fields are all null when I try to import. I tried adding a type converter as documented here, but it did not work.

ChoCSVRecordFieldConfiguration customStringConfig = new ChoCSVRecordFieldConfiguration("Address1");
customStringConfig.AddConverter(TypeDescriptor.GetConverter(typeof(CustomString)));
config.CSVRecordFieldConfigurations.Add(customStringConfig);
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Did you implement a type converter? See [c# how to implement type converter](https://stackoverflow.com/q/43649402/880990). – Olivier Jacot-Descombes Jul 02 '23 at 09:44
  • How would I tell ChoETL to use it? Are you saying to pass it in the AddConverters()? – Sora Teichman Jul 02 '23 at 09:45
  • Yes. I do not know ChoETL?, but apparently it needs a type converter. – Olivier Jacot-Descombes Jul 02 '23 at 09:47
  • I just tried that, same error. `ChoCSVRecordFieldConfiguration customStringConfig = new ChoCSVRecordFieldConfiguration("Address1"); customStringConfig.AddConverter(new CustomStringTypeConverter()); config.CSVRecordFieldConfigurations.Add(customStringConfig);` – Sora Teichman Jul 02 '23 at 10:03
  • Shouldn't it be something like `new ChoCSVRecordFieldConfiguration("Address1", 2);` where I assume that `"Address1"` is the name of the property in `CsvSalesOrder` (not shure about that) and 2 would be the column number in the CSV. If this is the case I would rather use `new ChoCSVRecordFieldConfiguration(nameof(CsvSalesOrder.Address1), 2);` to have the name double checked by the compiler. Otherwise ask a questron on the [Cinchoo ETL project site](https://www.codeproject.com/Articles/1145337/Cinchoo-ETL-CSV-Reader). – Olivier Jacot-Descombes Jul 02 '23 at 15:04
  • Sending in just the column name is valid, the column number is not required. I'm not sure the syntax for CsnSalesOrder.Address1 is correct, I'm getting an error that CsvSalesOrder does not have such a method. – Sora Teichman Jul 02 '23 at 16:42
  • If you want to import an address column, then `CsvSalesOrder` must have an address field or property. – Olivier Jacot-Descombes Jul 02 '23 at 21:57
  • I found the correct syntax for your idea but it's still not working... – Sora Teichman Jul 03 '23 at 07:17

3 Answers3

0

Add your own TypeConverter:

public class CustomStringTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string s) {
            return new CustomString(s);
        } else {
            return base.ConvertFrom(context, culture, value);
        }
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string) && value is CustomString customString) {
            return customString.Value;
        } else {
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}

I don't know ChoETL , therefore I am not sure if this fixes the problem or if there are other configuration problems.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
0

You do not need custom value converters, as CustomString already implemented implicit type conversion operators.

Here is the sample, on how do without converters

https://dotnetfiddle.net/uFrlCg

Here is a sample using converters

https://dotnetfiddle.net/ia5e50

Cinchoo
  • 6,088
  • 2
  • 19
  • 34
  • It is still returning null values for the CustomString fields. I added a converter and put the tag on the fields. One difference I see is that I am using a stream as opposed to a string input. Could that make a difference? I am using generic types as shown above. – Sora Teichman Jul 03 '23 at 17:35
  • pls share sample csv input / object model. If possible, write sample code in dotnetfiddle and share it. – Cinchoo Jul 03 '23 at 18:44
  • I modified your fiddle to more closely resemble my code, but I am getting an error that is not presenting on the fiddle - when it tries to do `r.AsTypedEnumerable();` it errors and says "Cannot access a disposed object.". This error appeared after I updated my version of ChoETL. [link]https://dotnetfiddle.net/wH9vqm – Sora Teichman Jul 04 '23 at 16:39
  • thanks for fiddle, after looking through sample, found few c# syntax issues. (esp. using statements are not used properly). Found incorrect mapping of `Address1` field. It must be mapped to `ShipToAddress.Address1`. Here is working fiddle https://dotnetfiddle.net/koYb7W – Cinchoo Jul 04 '23 at 22:53
  • Thank you so much! I had to add `.ToList()` after my `reader.AsTypedEnumerable()`, otherwise it still had the disposed object error. See updated fiddle here: https://dotnetfiddle.net/wH9vqm – Sora Teichman Jul 05 '23 at 09:23
0

I updated my code thanks to @Cinchoo's input.Here's the updated part:

using (var reader = new ChoCSVReader<T>(stream, config)
            .WithMaxScanRows(2)
            .QuoteAllFields()
            .IgnoreFieldValueMode(ChoIgnoreFieldValueMode.Any)
            )
        {
            return reader.AsTypedEnumerable<T>().ToList();
        }

I had to add .ToList() to get the list before the reader was disposed of. I also updated the using syntax and the version of the ChoETL package.