Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A better way to use AutoMapper to flatten nested objects?

I have been flattening domain objects into DTOs as shown in the example below:

public class Root {     public string AParentProperty { get; set; }     public Nested TheNestedClass { get; set; } }  public class Nested {     public string ANestedProperty { get; set; } }  public class Flattened {     public string AParentProperty { get; set; }     public string ANestedProperty { get; set; } }  // I put the equivalent of the following in a profile, configured at application start // as suggested by others:  Mapper.CreateMap<Root, Flattened>()       .ForMember        (           dest => dest.ANestedProperty           , opt => opt.MapFrom(src => src.TheNestedClass.ANestedProperty)        );  // This is in my controller: Flattened myFlattened = Mapper.Map<Root, Flattened>(myRoot); 

I have looked at a number of examples, and so far this seems to be the way to flatten a nested hierarchy. If the child object has a number of properties, however, this approach doesn't save much coding.

I found this example:

http://consultingblogs.emc.com/owainwragg/archive/2010/12/22/automapper-mapping-from-multiple-objects.aspx

but it requires instances of the mapped objects, required by the Map() function, which won't work with a profile as I understand it.

I am new to AutoMapper, so I would like to know if there is a better way to do this.

like image 801
John Avatar asked Jul 04 '11 21:07

John


2 Answers

In the latest version of AutoMapper, there's a naming convention you can use to avoid multiple .ForMember statements.

In your example, if you update your Flattened class to be:

public class Flattened {     public string AParentProperty { get; set; }     public string TheNestedClassANestedProperty { get; set; } } 

You can avoid the use of the ForMember statement:

Mapper.CreateMap<Root, Flattened>(); 

Automapper will (by convention) map Root.TheNestedClass.ANestedProperty to Flattened.TheNestedClassANestedProperty in this case. It looks less ugly when you're using real class names, honest!

like image 198
Jag Avatar answered Nov 09 '22 20:11

Jag


I much prefer avoiding the older Static methods and do it like this.

Place our mapping definitions into a Profile. We map the Root first, and then apply the mappings of the Nested afterwards. Note the use of the Context.

public class MappingProfile : Profile {     public MappingProfile()     {         CreateMap<Root, Flattened>()             .AfterMap((src, dest, context) => context.Mapper.Map(src.TheNestedClass, dest));         CreateMap<Nested, Flattened>();     } } 

The advantage of defining both the mapping from Root to Flattened and Nested to Flatterned is that you retain full control over the mapping of the properties, such as if the destination property name is different or you want to apply a transformation etc.

An XUnit test:

[Fact] public void Mapping_root_to_flattened_should_include_nested_properties() {     // ARRANGE     var myRoot = new Root     {         AParentProperty = "my AParentProperty",         TheNestedClass = new Nested         {             ANestedProperty = "my ANestedProperty"         }     };      // Manually create the mapper using the Profile     var mapper = new MapperConfiguration(cfg => cfg.AddProfile(new MappingProfile())).CreateMapper();      // ACT     var myFlattened = mapper.Map<Root, Flattened>(myRoot);      // ASSERT     Assert.Equal(myRoot.AParentProperty, myFlattened.AParentProperty);     Assert.Equal(myRoot.TheNestedClass.ANestedProperty, myFlattened.ANestedProperty); } 

By adding AutoMapper's serviceCollection.AddAutoMapper() from the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package to your start up, the Profile will be picked up automatically, and you can simply inject IMapper into wherever you are applying the mapping.

like image 30
Lee Oades Avatar answered Nov 09 '22 18:11

Lee Oades