Mitch Baker
Mitch Baker

Reputation: 63

How do you use CsvHelper to write a class derived from DynamicObject?

I was hoping to use a dynamically typed object to write to a CSV file.

I'm receiving a 'CsvHelper.CsvWriterException' within the CsvWriter.WriteObject method with this message: "No properties are mapped for type 'WpmExport.DynamicEntry'."

Here is the class that I'm trying use :

public class DynamicEntry : DynamicObject
{
    private Dictionary<string, object> dictionary = new Dictionary<string, object>();

    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        string name = binder.Name.ToLower();
        return dictionary.TryGetValue(name, out result);
    }

    public override bool TrySetMember(
        SetMemberBinder binder, object value)
    {
        dictionary[binder.Name.ToLower()] = value;
        return true;
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return dictionary.Keys.AsEnumerable();
    }
}

Anyone with any ideas or working examples? The documentation at http://joshclose.github.io/CsvHelper/ hints that it is possible but doesn't provide any guidance.

TIA

Upvotes: 3

Views: 3868

Answers (2)

FaustsErbe
FaustsErbe

Reputation: 113

Because I cannot wait for Version 3.0 (and CsvHelper.Excel to support it), I have found a interim-solution.

Got the class to export:

public partial class EntryReportInventory
{
    public Guid DeviceId { get; set; }
    [ReportProperty]
    public string DeviceName { get; set; }

    public Dictionary<string, object> InventoryValues { get; set; }

    public EntryReportInventory(Device device, Dictionary<string, object> inventoryValues)
    {
        this.DeviceId = device.Id;
        this.DeviceName = device.Name;

        this.InventoryValues = inventoryValues;
    }
}

Created Mapper:

Type genericClass = typeof(DefaultCsvClassMap<>);
Type constructedClass = genericClass.MakeGenericType(typeof(EntryReportInventory));
                return (CsvClassMap)Activator.CreateInstance(constructedClass);

And now the magic. I iterate all properties.

foreach (PropertyInfo property in mapping)
            {
...
if (isInventoryReportBaseType && typeof(Dictionary<string, object>).IsAssignableFrom(property.PropertyType))
                {
                    var dataSource = (ReportInventoryBase)Activator.CreateInstance(entityType, dbContext);

                    foreach (var item in dataSource.ColumnNameAndText)
                    {
                        var columnName = item.Key;

                        var newMap = new CsvPropertyMap(property);
                        newMap.Name(columnName);
                        newMap.TypeConverter(new InventoryEntryListSpecifiedTypeConverter(item.Key));

                        customMap.PropertyMaps.Add(newMap);
                    }
...
}

And my converter is:

    public class InventoryEntryListSpecifiedTypeConverter : CsvHelper.TypeConversion.ITypeConverter
    {
        private string indexKey;
        public InventoryEntryListSpecifiedTypeConverter(string indexKey)
        {
            this.indexKey = indexKey;
        }

        public bool CanConvertFrom(Type type)
        {
            return true;
        }

        public bool CanConvertTo(Type type)
        {
            return true;
        }

        public object ConvertFromString(TypeConverterOptions options, string text)
        {
            throw new NotImplementedException();
        }

        public string ConvertToString(TypeConverterOptions options, object value)
        {
            var myValue = value as Dictionary<string, object>;
            if (value == null || myValue.Count == 0) return null;

            return myValue[indexKey] + "";
        }
    }

Don't know why, but it works to pass the same property several times. That's it :) You only have to have a list before (here: dataSource.ColumnNameAndText, filled from an external source) to identify the columns/values.

Upvotes: 2

Josh Close
Josh Close

Reputation: 23383

The functionality does not exist yet. You can write dynamic but not DynamicObject. You can view a thread on the subject here. https://github.com/JoshClose/CsvHelper/issues/187

When the functionality get implemented, I'll update the answer with the version it's in.

Update

This functionality will be available in 3.0. You can currently try out the 3.0-beta from NuGet.

Upvotes: 4

Related Questions