简体   繁体   中英

AutoMapper: Map to collection from multiple source properties

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?

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;
    }
}

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

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 :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM