5

I know various questions have been asked that resembles this question, but as far as I can tell (and test), none of the provided solutions seems to fit, so here goes.

I am wondering if it is possible to flatten/denormalise an object hierarchy so that an instance with a list of nested properties is mapped to a list of some destination type using AutoMapper.

I have a source class that looks something like

Sources:

public class DistributionInformation
{
   public string Streetname;
   public RouteInformation[] Routes;
}

public class RouteInformation
{
   public int RouteNumber;
   public string RouteDescription;
}

Destination:

public class DenormDistributionInfo
{
   public string Streetname;
   public int RouteNumber;
   public string RouteDescription;
}

So I want to map the two sources to a list of the denormalised destination DenormDistributionInfo.

I.e:

IEnumerable<DenormDistributionInfo> result = Mapper.Map(distributionInformationInstance);

Is that possible/feasible using AutoMapper, or should I give in and denormalise it "manually"?

Esben Bach
  • 664
  • 4
  • 23
  • IMO, using Automapper beyond the very simple case of mapping types with identical properties to one another is an abuse of Automapper. The temptation becomes to hide all sorts of application logic in your mappings, at which point, you've created a system that's harder to understand than a more conventional approach. (Disclosure: I don't like Automapper... [Friends don't let friends use Automapper](http://www.uglybugger.org/software/post/friends_dont_let_friends_use_automapper).) – spender May 05 '15 at 09:47

2 Answers2

4

The main thing is that you want to avoid having to "look up" data in your mapping that isn't implicit in the source. "Magical" mappings cause serious maintenance problems down the line.

Conceptually, however, this mapping is pretty simple. The only complicated factor is that you need two source objects (both a DistributionInformation and a RouteInformation) in order to construct your target object. If you follow that train of thought, we can create a non-magical mapping that clearly preserves our intent - here's how I'd do it:-

// We need both source objects in order to perform our map
Mapper.CreateMap<Tuple<DistributionInformation, RouteInformation>, DenormDistributionInfo>()
      .ForMember(d => d.Streetname, o => o.MapFrom(s => s.Item1.Streetname))
      .ForMember(d => d.RouteDescription, o => o.MapFrom(s => s.Item2.RouteDescription))
      .ForMember(d => d.RouteNumber, o => o.MapFrom(s  => s.Item2.RouteNumber));

// We can use ConstructUsing to pass both our source objects to our map
Mapper.CreateMap<DistributionInformation, IEnumerable<DenormDistributionInfo>>()
      .ConstructUsing(
          x => x.Routes
                .Select(y => Mapper.Map<DenormDistributionInfo>(Tuple.Create(x, y)))
                .ToList());

And to invoke it:-

var flattened = Mapper.Map<IEnumerable<DenormDistributionInfo>>(source);

You can avoid a little of the Tuple horror if you like by creating a DTO to hold both source objects. I especially strongly recommend this if your real code is even slightly more involved than the example you've presented in your question.

Whether or not using AutoMapper to perform this mapping is more or less complicated than just doing it by hand is up to you to decide. In this case, I don't think I'd bother, but in a more involved scenario that gets repeated often I might consider it.

Iain Galloway
  • 18,669
  • 6
  • 52
  • 73
  • Okay got it. Seems like in my case I might as well map it manually, in any event the number of exceptions from the convention means it is troublesome regardless. Thanks for the info. – Esben Bach May 05 '15 at 11:20
0

I dug into it a bit, and while I choose to solve the problem by mapping "manually" there is another way (aside from the answer posted by Iain. It does feel rather hacky though.

The idea is to use a type converter and map it twice

    public class DistributionInfoConverter : ITypeConverter<DistributionInformation, IEnumerable<DenormDistributionInfo>>
    {
        public IEnumerable<DenormDistributionInfo> Convert(ResolutionContext context)
        {
            var result = new List<DenormDistributionInfo>();
            var source = (DistributionInformation)context.SourceValue;

            foreach (var routeDetail in source.Routes)
            {
                var model = new DenormDistributionInfo();
                Mapper.Map(routeDetail, model);
                Mapper.Map(source, model);
                result.Add(model);
            }

            return result;
        }
    }

    Mapper.CreateMap<RouteInformation, DenormDistributionInfo>();
    Mapper.CreateMap<DistributionInformation, DenormDistributionInfo>()
    Mapper.CreateMap<DistributionInformation, IEnumerable<DenormDistributionInfo>>().ConvertUsing<DistributionInfoConverter>();

The only issue is that for collections of DistributionInformation you have to loop/select each item and map instead of letting automapper figure out how to map a collection to a collection like you normally would.

Esben Bach
  • 664
  • 4
  • 23