AutoMapper and flattening nested arrays
Asked Answered
B

3

9

I'm trying to use AutoMapper to flatten multiple levels of arrays.

Consider the following source classes:

class X {
    public string A { get; set; }
    public Y[] B { get; set; }
}

class Y {
    public string C { get; set; }
    public Z[] D { get; set; }
}

class Z {
    public string E { get; set; }
    public string F { get; set; }
}

And the following destination:

class Destination {
    public string A { get; set; }
    public string C { get; set; }
    public string E { get; set; }
    public string F { get; set; }
}

What I'd like to be able to do is get a List from one or more X, e.g.:

Mapper.Map<IEnumerable<X>, IEnumerable<Destination>>(arrayOfX);

I'm unable to figure out what sort of mapping configuration to use to achieve this. MapFrom seems like the way to go for 1:1 compositions, but doesn't seem to be able to handle the array (or other enumerable) unless I use AutoMapper's destination naming convention.

Any insights on how to achieve this?

Brachycephalic answered 12/11, 2012 at 4:8 Comment(0)
C
9

Try this mapper,

Mapper.CreateMap<Z, Destination>();
Mapper.CreateMap<Y, Destination>();
Mapper.CreateMap<X, Destination>()
    .ForMember(destination => destination.A, options => options.MapFrom(source => source.A)).IgnoreAllNonExisting()
    .ForMember(destination => destination.C, options => options.MapFrom(source => Mapper.Map<IEnumerable<Y>, IEnumerable<Destination>>(source.B).FirstOrDefault().C))
    .ForMember(destination => destination.E, options => options.MapFrom(source => Mapper.Map<IEnumerable<Z>, IEnumerable<Destination>>(source.B.SelectMany(d => d.D)).FirstOrDefault().E))
    .ForMember(destination => destination.F, options => options.MapFrom(source => Mapper.Map<IEnumerable<Z>, IEnumerable<Destination>>(source.B.SelectMany(d => d.D)).FirstOrDefault().F));

var result = Mapper.Map<IEnumerable<X>, IEnumerable<Destination>>(arrayOfX);
Caffrey answered 12/11, 2012 at 4:50 Comment(2)
That will throw an AutoMapperMappingException if B or D is null or zero-length array. X[] arrayOfX = new X[] {new X() {A = "a1", B = null}, new X() {A = "a2", B = new Y[] {}}};Ultramicrochemistry
@JayWalker Edited my post. Just added the null check. Thanks for finding.Caffrey
U
4

I had a very similar problem a while ago. I had a collection of locations, and each location had a collection of streets. I wanted to map them to a collection of view models where each view model represented a street (including the location details).

This was my solution: https://groups.google.com/forum/#!topic/automapper-users/b66c1M8eS8E

For this particular problem, this could be your mapping configuration:

public static class AutoMapperConfig
{
     public static void Configure()
     {
         Mapper.CreateMap<Z, Destination>()
             .ForMember(dest => dest.A, opt => opt.Ignore())
             .ForMember(dest => dest.C, opt => opt.Ignore());

         Mapper.CreateMap<Y, Destination>()
             .ForMember(dest => dest.A, opt => opt.Ignore())
             .ForMember(dest => dest.E, opt => opt.Ignore())
             .ForMember(dest => dest.F, opt => opt.Ignore());

         Mapper.CreateMap<X, Destination>()
             .ForMember(dest => dest.C, opt => opt.Ignore())
             .ForMember(dest => dest.E, opt => opt.Ignore())
             .ForMember(dest => dest.F, opt => opt.Ignore());
     }
}

Because AutoMapper is primarily a 1:1 mapping, you need to implement a wee bit of magic to map to multiple objects. This is an example of how you could call that mapping to populate your object:

var rc = data.SelectMany(
    x => x.B.SelectMany(
        y => y.D
            .Select(Mapper.Map<Z, Destination>)
            .Select(z => Mapper.Map(y, z))
        )
        .Select(y => Mapper.Map(x, y))
    );

Here are a couple of unit tests to validate the mapping and show it in action:

[TestFixture]
public class MapperTests
{
    [Test]
    public void Mapping_Configuration_IsValid()
    {
        AutoMapperConfig.Configure();
        Mapper.AssertConfigurationIsValid();
    }

    [Test]
    public void Mapping_TestItems_MappedOK()
    {
        AutoMapperConfig.Configure();
        Mapper.AssertConfigurationIsValid();

        var data = new[]
            {
                new X
                    {
                        A = "A1",
                        B = new[]
                            {
                                new Y
                                    {
                                        C = "A1C1",
                                        D = new[]
                                            {
                                                new Z
                                                    {
                                                        E = "A1C1E1",
                                                        F = "A1C1F1"
                                                    },
                                                new Z
                                                    {
                                                        E = "A1C1E2",
                                                        F = "A1C1F2"
                                                    },
                                            }
                                    },
                                new Y
                                    {
                                        C = "A1C2",
                                        D = new[]
                                            {
                                                new Z
                                                    {
                                                        E = "A1C2E1",
                                                        F = "A1C2F1"
                                                    },
                                                new Z
                                                    {
                                                        E = "A1C2E2",
                                                        F = "A1C2F2"
                                                    },
                                            }
                                    }
                            }
                    }
            };

        var rc = data.SelectMany(
            x => x.B.SelectMany(
                y => y.D
                    .Select(Mapper.Map<Z, Destination>)
                    .Select(z => Mapper.Map(y, z))
                )
                .Select(y => Mapper.Map(x, y))
            );

        Assert.That(rc, Is.Not.Null);
        Assert.That(rc.Count(), Is.EqualTo(4));
        var item = rc.FirstOrDefault(x => x.F == "A1C2F2");
        Assert.That(item, Is.Not.Null);
        Assert.That(item.A, Is.EqualTo("A1"));
        Assert.That(item.C, Is.EqualTo("A1C2"));
        Assert.That(item.E, Is.EqualTo("A1C2E2"));
        Assert.That(item.F, Is.EqualTo("A1C2F2"));
    }
}
Unfledged answered 12/11, 2012 at 5:21 Comment(0)
A
1

For anyone else coming across this post by searching how to Flatten object structure with AutoMapper - the new AutoMapper supports flattening with IncludeMembers() syntax.

Source: http://docs.automapper.org/en/stable/Flattening.html

So the original issue could be solved like this:

Mapper.CreateMap<Z, Destination>();
Mapper.CreateMap<Y, Destination>().IncludeMembers(src => src.D);
Mapper.CreateMap<X, Destination>().IncludeMembers(src => src.B);
Alurd answered 24/5, 2019 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.