Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Denormalise object hierarchy with automapper

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"?

like image 862
Esben Bach Avatar asked Sep 29 '22 01:09

Esben Bach


1 Answers

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.

like image 64
Iain Galloway Avatar answered Nov 15 '22 05:11

Iain Galloway