Reputation: 1086
UPDATE April 13th, 2018: Automapper 6.1.0 supports unflattening by introducing ReverseMap
. See release notes here
I'm trying to use AutoMapper to unflatten an object.
I have a source as follows
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;}
}
I want to map to this to destination
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;}
}
My mapping configuration
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;
}
}
Now when I test my code I get using mapper.Map<List<Child>>(source)
I get a AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Which make sense, since there isn't a mapping configured to a List<Child>
. If I do mapper.Map<Child>(source)
, I get a Child
instance with all null
values for the properties.
I'm unfortunately not in a position to modify the Source
class.
Is this possible at all with AutoMapper? and if so how?
Upvotes: 6
Views: 6602
Reputation: 1250
Instead of using a custom type converter, it may be better to use a custom value resolver and leave the rest of the mapping to AutoMapper. In this case, it's not difficult to map source.Name
to destination.Name
, but imagine you had 10 other properties that AutoMapper could handle, or that you could use the default opt.MapFrom
for.
Example custom value resolver:
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;
}
}
Configuration to use resolver:
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;
}
}
One thing that could help clarify my solution for myself is how is List<Child> member
exactly used. Was not clear for me in the documentation, so someone please comment :)
Upvotes: 0
Reputation: 1369
There are at least 2 options. You can use a simple extension method to simplify the mapping or you can create a custom type converter.
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;
}
}
Upvotes: 3
Reputation: 1
You can add a method on Source class to fetch the child list. Then it's very easy to map .
Upvotes: 0