40

I've been trying to use AutoMapper to save some time going from my DTOs to my domain objects, but I'm having trouble configuring the map so that it works, and I'm beginning to wonder if AutoMapper might be the wrong tool for the job.

Consider this example of domain objects (one entity and one value):

public class Person
{
    public string Name { get; set; }
    public StreetAddress Address { get; set; }
}

public class StreetAddress
{
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

My DTO (from a Linq-to-SQL object) is coming out looking roughly like this:

public class PersonDTO
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

I'd like to be able to do this in my repository:

return Mapper.Map<PersonDTO, Person>(result);

I've tried configuring AutoMapper every way I can figure, but I keep getting the generic Missing type map configuration or unsupported mapping error, with no details to tell me where I'm failing.

I've tried a number of different configurations, but here are a few:

Mapper.CreateMap<PersonDTO, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));

and

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
    .ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
    .ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));

I've read that flattening objects with AutoMapper is easy, but unflattening them isn't easy...or even possible. Can anyone tell me whether I'm trying to do the impossible, and if not what I'm doing wrong?

Note that my actual objects are a little more complicated, so it's possible I'm leaving out info that is the key to the error...if what I'm doing looks right I can provide more info or start simplifying my objects for testing.

Daniel Schilling
  • 4,829
  • 28
  • 60
Josh Anderson
  • 5,975
  • 2
  • 35
  • 48
  • Hmmm your second configuration looks sweet (aside from its missing Name) and shouldn't it be ? Might pay to check Domain.Person and PersonDTO are correct references to your classes mentioned above – Scott Jul 01 '10 at 04:49
  • related: http://stackoverflow.com/questions/8159186/automapper-map-dto-back-to-domain-object-with-child-objects – Ruben Bartelink Jul 25 '12 at 19:53
  • 1
    At this time the link from Ruben and the accepted answer (only one answer right now) from 81959186 points back to this post. I don't see the value to the link comment on this page. Ruben posted a link to this question from the other page and that one makes sense. – Charles Byrne Dec 10 '14 at 14:47

7 Answers7

71

This also seems to work for me:

Mapper.CreateMap<PersonDto, Address>();
Mapper.CreateMap<PersonDto, Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

Basically, create a mapping from the dto to both objects, and then use it as the source for the child object.

sydneyos
  • 4,527
  • 6
  • 36
  • 53
  • 1
    This is exactly what I was looking for. ValueInjector may be the selected answer, but with this, AutoMapper seems way easier to use. – esteuart Jul 19 '12 at 20:49
  • 12
    This would be a great answer, but in my DTO the address properties are flattened: "AddressCity", whereas in the Address object they are just "City", so the mapping does not work unless I explicitly map each field. Is this what you had to do as well? – Trevor de Koekkoek Nov 08 '12 at 13:36
  • This works nicely but it does not allow null values to actually be set. It's ignoring null values and retaining the destination's original value. Is it possible to write actually accept and write the null values? – Adam Levitt Sep 05 '14 at 01:22
  • @TrevordeKoekkoek: Did you ever find a solution? I'm in the same situation now. – Cody Dec 17 '14 at 20:04
  • Sorry @Cody I don't really remember specifically. I do know that I have since used Omu ValueInjecter which has worked better for me in some scenarios. http://valueinjecter.codeplex.com/ – Trevor de Koekkoek Dec 18 '14 at 20:35
  • I was trying to do exactly this, but with `opt.MapFrom( src => Mapper.Instance.Map....)` which felt so wrong, and this is so much simpler :-) – Simon_Weaver Feb 15 '19 at 02:08
  • So simple I could cry I didn't figure out myself. Thanks! @TrevordeKoekkoek The given solution works perfectly for the OPs problem. – Steven Feb 09 '22 at 11:10
9

Can't post a comment, so posting an answer. I guess there were some changes in AutoMapper implementation so answer https://stackoverflow.com/a/5154321/2164198 proposed by HansoS is no longer compilable. Though there is another method that can be used in such scenarios - ResolveUsing:

Mapper.CreateMap<Person, Domain.Person>()
    .ForMember(dest => dest.Address, opt => opt.ResolveUsing( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
Community
  • 1
  • 1
Ivan Samygin
  • 4,210
  • 1
  • 20
  • 33
  • 6
    This works but it kind of goes against the whole principle of AutoMapper. It's not a convention based "Auto" map anymore, it's manual mapping. Worse still, it's manual mapping which is obscured behind an opaque façade. – MattDavey Oct 16 '13 at 13:22
  • 2
    From [Automapper wiki](https://github.com/AutoMapper/AutoMapper/wiki): > Currently, AutoMapper is geared towards model projection scenarios to flatten complex object models to DTOs and other simple objects, whose design is better suited for serialization, communication, messaging, or simply an anti-corruption layer between the domain and application layer. So, Automapper is not intended to convert DTO->Entity. But it has a lot of features that you can use as you want. It's up to you - Automapper is just a tool. – Ivan Samygin Oct 24 '13 at 11:15
9

In addition to sydneyos answer and according to Trevor de Koekkoek comment, two way mapping is possible this way

public class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

public class PersonViewModel
{
    public string Name { get; set; }
    public string AddressStreet { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
}

Automapper mappings

Mapper.Initialize(cfg => cfg.RecognizePrefixes("Address"));
Mapper.CreateMap<Person, PersonViewModel>();
Mapper.CreateMap<PersonViewModel, Address>();
Mapper.CreateMap<PersonViewModel, Person>()
    .ForMember(dest => dest.Address, opt => opt.MapFrom( src => src )));

If you implement NameOf class, you can get rid of prefix magic string

Mapper.Initialize(cfg => cfg.RecognizePrefixes(Nameof<Person>.Property(x => x.Address)));

EDIT: In C# 6

Mapper.Initialize(cfg => cfg.RecognizePrefixes(nameof(Person.Address)));
sanjuro
  • 1,611
  • 1
  • 21
  • 29
8

use https://github.com/omuleanu/ValueInjecter, it does flattening and unflattening, and anything else you need, there is an asp.net mvc sample application in the download where all the features are demonstrated (also unit tests)

Omu
  • 69,856
  • 92
  • 277
  • 407
  • 2
    I had been trying ValueInjecter and ran into some issues that you helped me through on CodePlex. Right now it's working pretty well. – Josh Anderson Jul 01 '10 at 20:47
  • valueInjector does the job in more elegant way than AutoMapper – Mario Zderic Nov 08 '12 at 16:21
  • it doesnt support lists though – GorillaApe Dec 14 '12 at 00:21
  • This project is dead – Yacov Jun 08 '17 at 11:33
  • 1
    @Yacov it was moved to github – Omu Jun 08 '17 at 12:48
  • @Omu: do you mind sharing code examples in this post? I had hope on ValueInjecter but I switched back to AutoMapper because of the documentation. I couldn't find the ASP.NET MVC sample application you were talking about in the download. Can you post a link? All I saw were WebForm, WinForm and WPF examples. – David Liang Feb 16 '21 at 21:16
  • @DavidLiang there are some injection examples here: https://github.com/omuleanu/ValueInjecter/wiki/custom-injections-examples – Omu Feb 18 '21 at 20:47
6

This might be late but you can solve this by using lambda expressions to create the object like this:

Mapper.CreateMap<Person, Domain.Person>()
        .ForMember(dest => dest.Address, opt => opt.MapFrom( src => { return new Address() {Address1 = src.Address, City = src.City, State = src.State }; }))
Romano Zumbé
  • 7,893
  • 4
  • 33
  • 55
HansoS
  • 172
  • 1
  • 3
  • 1
    This works great - not sure how it would handle the situation where Address already exists on the target object. – sydneyos May 04 '11 at 22:16
  • 7
    This results in an error: 'A lambda expression with a statement body cannot be converted to an expression tree' – Kwal Nov 20 '12 at 16:34
  • 1
    I get the same error @Kwal mentions. However, when I replaced the `opt.MapFrom` with `opt.ResolveUsing`, as per Ivan Samygin's answer, it did work. – Simon Elms Jun 15 '16 at 23:48
2

I have another solution. The main idea is that AutoMapper know how to flatten nested objects when you name properly properties in flattened object: adding nested object property name as a prefix. For your case Address is prefix:

public class PersonDTO
{
    public string Name { get; set; }
    public string AddressCity { get; set; }
    public string AddressState { get; set; }
    ...
}

So creating familiar mapping from nested to flattened and then using ReverseMap method allows AutomMapper to understand how to unflatten nested object.

CreateMap<Person, PersonDTO>()
   .ReverseMap();

That's all!

Andrei
  • 749
  • 6
  • 7
1

I'm using this

public static void Unflatten<TSource, TDestination, TMember>(this IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
    var prefix = opt.DestinationMember.Name;
    var memberProps = typeof(TMember).GetProperties();
    var props = typeof(TSource).GetProperties().Where(p => p.Name.StartsWith(prefix))
        .Select(sourceProp => new
        {
            SourceProp = sourceProp,
            MemberProp = memberProps.FirstOrDefault(memberProp => prefix + memberProp.Name == sourceProp.Name)
        })
        .Where(x => x.MemberProp != null);
    var parameter = Expression.Parameter(typeof(TSource));

    var bindings = props.Select(prop => Expression.Bind(prop.MemberProp, Expression.Property(parameter, prop.SourceProp)));
    var resolver = Expression.Lambda<Func<TSource, TMember>>(
        Expression.MemberInit(Expression.New(typeof(TMember)), bindings),
        parameter);

    opt.ResolveUsing(resolver.Compile());
}

Configuration

new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonDTO>();
    cfg.CreateMap<PersonDTO, Person>().ForMember(x => x.HomeAddress, opt => opt.Unflatten());
});

Models

public class Person
{
    public string Name { get; set; }
    public Address HomeAddress { get; set; }
}

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

Following AutoMapper flattening conventions

public class PersonDTO
{
    public string Name { get; set; }
    public string HomeAddressLine1 { get; set; }
    public string HomeAddressLine2 { get; set; }
    public string HomeAddressCity { get; set; }
    public string HomeAddressState { get; set; }
    public string HomeAddressZipCode { get; set; }
}

Probably needs many improvements but it works...

andres.chort
  • 836
  • 7
  • 9