AutoMapper:映射到来自多个源属性的集合

本文关键字:属性 集合 映射 AutoMapper | 更新日期: 2023-09-27 18:15:17

2018年4月13日更新: Automapper 6.1.0通过引入ReverseMap支持非扁平化。见发行说明这里

我正在尝试使用AutoMapper来平展一个对象。

我有一个来源如下

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

我想把这个映射到目的地

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

我的映射配置

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

现在,当我测试我的代码时,我使用mapper.Map<List<Child>>(source),我得到一个AutoMapperMappingException: Missing type map configuration or unsupported mapping.,这是有意义的,因为没有一个映射配置到List<Child>。如果我做mapper.Map<Child>(source),我得到一个Child实例与所有null值的属性。

不幸的是,我不能修改Source类。

这在AutoMapper中是可能的吗?如果是,那又是怎么回事呢?

AutoMapper:映射到来自多个源属性的集合

至少有2个选项。您可以使用简单的扩展方法来简化映射,也可以创建自定义类型转换器。

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

您可以在Source类中添加一个方法来获取子列表。这样就很容易映射了

与其使用自定义类型转换器,不如使用自定义值解析器,并将其余的映射留给AutoMapper。在这种情况下,将source.Name映射到destination.Name并不困难,但是假设您有10个AutoMapper可以处理的其他属性,或者您可以使用默认的opt.MapFrom

自定义值解析器示例:

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

使用解析器的配置:

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

有一件事可以帮助澄清我自己的解决方案是如何使用List<Child> member。在文档中我不清楚,所以请有人评论:)