6
votes

UPDATE April 13th, 2018: Automapper 6.1.0 supports unflattening by introducing ReverseMap. See release notes here

I'm trying to use AutoMapper to unflatten an object.

I have a source as follows

public class Source
{
    public string Name {get;set;}
    public string Child1Property1 {get;set;}
    public string Child1Property2 {get;set;}
    public string Child2Property1 {get;set;}
    public string Child2Property2 {get;set;}
}

I want to map to this to destination

public class Destination
{
    public string Name {get;set;}
    public List<Child> Children {get;set;}
}

public class Child
{
    public string Property1 {get;set;}
    public string Property2 {get;set;}
}

My mapping configuration

public static class AutoMapperConfiguration
{
    public static MapperConfiguration Configure()
    {
        var config = new MapperConfiguration(
            cfg =>
            {
                cfg.CreateMap<Source, Destination>()
                    .ForMember(dest => dest.Children, /* What do I put here?*/))
                // I don't think this is correct
                cfg.CreateMap<Source, Child>()
                    .ForMember(dest => dest.Property1, opt => opt.MapFrom(src => src.Child1Property1))
                    .ForMember(dest => dest.Property2, opt => opt.MapFrom(src => src.Child1Property2))
                    .ForMember(dest => dest.Property1, opt => opt.MapFrom(src => src.Child2Property1))
                    .ForMember(dest => dest.Property2, opt => opt.MapFrom(src => src.Child2Property2));

            });
        return config;
    }
}

Now when I test my code I get using mapper.Map<List<Child>>(source) I get a AutoMapperMappingException: Missing type map configuration or unsupported mapping. Which make sense, since there isn't a mapping configured to a List<Child>. If I do mapper.Map<Child>(source), I get a Child instance with all null values for the properties.

I'm unfortunately not in a position to modify the Source class.

Is this possible at all with AutoMapper? and if so how?

3
I see in the April 13th, 2018 that you include a link to ReverseMap which is supposed to help with unflattening, but I still don't know how it shows this scenario. Could whoever updated it include some sample code that would answer the OP's question?ahong

3 Answers

3
votes

There are at least 2 options. You can use a simple extension method to simplify the mapping or you can create a custom type converter.

public class ConvertSourceToDestination : ITypeConverter<Source, Destination>
{
    public Destination Convert(Source source, Destination destination, ResolutionContext context)
    {
        destination = destination ?? new Destination();
        destination.Children = destination.Children ?? new List<Child>();
        destination.Children.Add(new Child() { Property1 = source.Child1Property1, Property2 = source.Child1Property2 });
        destination.Children.Add(new Child() { Property1 = source.Child2Property1, Property2 = source.Child2Property2 });
        destination.Name = source.Name;

        return destination;
    }
}

public static class SourceExtension
{
    public static IEnumerable<Child> Children(this Source source)
    {
        yield return new Child() { Property1 = source.Child1Property1, Property2 = source.Child1Property2 };
        yield return new Child() { Property1 = source.Child2Property1, Property2 = source.Child2Property2 };
    }

    public static MapperConfiguration CreateMapping()
    {
        var config = new MapperConfiguration(
            cfg =>
            {
                cfg.CreateMap<Source, Destination>()
                .ForMember(dest => dest.Children, opt => opt.MapFrom(src => src.Children()));
            });

        return config;
    }

    public static MapperConfiguration CreateMapping2()
    {
        var config = new MapperConfiguration(
            cfg =>
            {
                cfg.CreateMap<Source, Destination>().ConvertUsing(new ConvertSourceToDestination());

            });

        return config;
    }
}
0
votes

You can add a method on Source class to fetch the child list. Then it's very easy to map .

0
votes

Instead of using a custom type converter, it may be better to use a custom value resolver and leave the rest of the mapping to AutoMapper. In this case, it's not difficult to map source.Name to destination.Name, but imagine you had 10 other properties that AutoMapper could handle, or that you could use the default opt.MapFrom for.

Example custom value resolver:

public class SourceToDestinationChildResolver : IValueResolver<Source, Destination, List<Child>>
{
    public List<Child> Resolve(Source source, Destination destination, List<Child> member, ResolutionContext context)
    {
        destination = destination ?? new Destination();
        destination.Children = destination.Children ?? new List<Child>();
        destination.Children.Add(new Child() { Property1 = source.Child1Property1, Property2 = source.Child1Property2 });
        destination.Children.Add(new Child() { Property1 = source.Child2Property1, Property2 = source.Child2Property2 });
        // This is not needed then
        // destination.Name = source.Name;
        return destination.Children;
    }
}

Configuration to use resolver:

public static class AutoMapperConfiguration
{
    public static MapperConfiguration Configure()
    {
        var config = new MapperConfiguration(
            cfg =>
            {
                cfg.CreateMap<Source, Destination>()
                   .ForMember(dest => dest.Children, opt => opt.MapFrom<SourceToDestinationChildResolver>())
            });
        return config;
    }
}

One thing that could help clarify my solution for myself is how is List<Child> member exactly used. Was not clear for me in the documentation, so someone please comment :)