Mapping an IEnumerable property with CSVHelper

5.4k views Asked by At

I have a class of User, which can have several contact numbers. I am using CsvHelper to generate a report on the users, which will create a CSV file of the User's name and contact details. Each contact number should be displayed in its own column, with the contact number's type as the column header.

Below I have my ContactNumber and User classes, as well as a UserMap class that should format my CSV file accordingly:

public class ContactNumber
{
    public string ContactType { get; set; }
    public string Number { get; set; }
}

public class User
{
    public string Name { get; set; }
    public IEnumerable<ContactNumber> ContactNumbers{ get; set;}
}

public sealed class UserMap : CsvClassMap<User>
{
    public UserMap()
    {
        Map(m => m.Name).Name("Username");
        // Incorrect Code
        Map(m => m.ContactNumbers).Name(c => c.ContactType);
    }
}

How should I be mapping the IEnumerable property of ContactNumbers to achieve my desired result?

3

There are 3 answers

0
Joost Bollen On BEST ANSWER

It is not a duplicate of CsvHelper - read in multiple columns to a single list as this question asks how to convert from model to CSV and not the other way around. I solved this by creating an in-between model. So first you convert the original model (User) to your in between model (Contact), then map that model and create the CSV.

0
Sigex On

I like your solution @Humble Rumble

public string ConcatenatedUserNumbers
    {
        get
        {
            if (ContactNumbers != null && ContactNumbers.Any())
            {
                return string.Join("; ", ContactNumbers.Select(a => a.Number));
            }
            return string.Empty;
        }
    }

Here it is as a lambda

    public string ConcatenatedUserNumbers =>
        (this.ContactNumbers != null && this.ContactNumbers .Any())
        ? string.Join("; ", this.ContactNumbers .Select(a => a.Number))
        : string.Empty;
0
Humble Rumble On

I know this an old question but I have created another solution by putting the following property on my DTO (User class).

public string ConcatenatedUserNumbers
    {
        get
        {
            if (ContactNumbers != null && ContactNumbers.Any())
            {
                return string.Join("; ", ContactNumbers.Select(a => a.Number));
            }
            return string.Empty;
        }
    }

I then just map to this property in CsvHelper like so:

Map(a => a.ConcatenatedUserNumbers).Name("User Numbers");

You could obviously do the same for user types, or have a method that gets both. The data may not be perfect but the end user can still use the 'filter' functionality within excel and it will be human readable. All without the need for excess mappings etc.

Note: If this extra property is only for a CSV mapping you could also make a class CSVUser : User. You would put the suggested property on CSVUser only.

I hope this helps somebody out.