Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you map a Dto to an existing object instance with nested objects using AutoMapper?

I have the following Dto and entity with a nested sub entity.

public class Dto
{
    public string Property { get; set; }
    public string SubProperty { get; set; }
}

public class Entity
{
    public string Property { get; set; }
    public SubEntity Sub { get; set; }
}

public class SubEntity
{
    public string SubProperty { get; set; }
}

How can I set up a mapping with AutoMapper that will allow me to update an existing instance of Entity with the values from a Dto.

I'm using Mapper.Map(dto, entity) to update an existing entity but when I try to map Dto.SubProperty to Entity.Sub.SubProperty I get an exception for "must resolve to top-level member. Parameter name: lambdaExpression".

If I create a mapping from Dto to SubEntity using FromMember then Entity.Sub gets replaced with a new instance of SubEntity but that's not what I want. I just want it to update the properties of the existing instance of SubEntity on the Sub property of Entity.

How can I achieve this?

like image 593
John Mills Avatar asked Sep 08 '10 22:09

John Mills


People also ask

Does AutoMapper map nested objects?

With both flattening and nested mappings, we can create a variety of destination shapes to suit whatever our needs may be.

Can AutoMapper map collections?

AutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.

What is DTO mapper?

DTO, which stands for Data Transfer Object, is a design pattern conceived to reduce the number of calls when working with remote interfaces.

How does AutoMapper work in C#?

AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.


1 Answers

I solved it by using a combination of the ResolveUsing<T>() method and implementing IValueResolver and the ConvertUsing<T>() method and implementing ITypeConverter<TSource,TDestination>.

Some of my mapping scenarios are more complicated than normal including bidirectional mapping and nested classes and nested collections. The above helped me to solve them.


EDIT

As requested, I've included an example solution. This example is much simpler than the actual types I was dealing with.

using System;
using AutoMapper;

namespace TestAutoMapperComplex
{
    public class Dto
    {
        public string Property { get; set; }
        public string SubProperty { get; set; }
    }

    public class Entity
    {
        public string Property { get; set; }
        public SubEntity Sub { get; set; }
    }

    public class SubEntity
    {
        public string SubProperty { get; set; }
    }

    static class MapperConfig
    {
        public static void Initialize()
        {
            Mapper.CreateMap<Dto, Entity>()
                .ForMember(entity => entity.Sub, memberOptions =>
                    memberOptions.MapFrom(dto => dto));
            Mapper.CreateMap<Dto, SubEntity>();
        }
    }

    static class MapperConfig2
    {
        private class MyResolver : IValueResolver
        {

            public ResolutionResult Resolve(ResolutionResult source)
            {
                var destinationSubEntity = ((Entity)source.Context.DestinationValue).Sub;

                Mapper.Map((Dto)source.Value, destinationSubEntity);

                return source.New(destinationSubEntity, typeof(SubEntity));
            }
        }

        public static void Initialize()
        {
            Mapper.CreateMap<Dto, Entity>()
                .ForMember(entity => entity.Sub, memberOptions =>
                    memberOptions.ResolveUsing<MyResolver>());
            Mapper.CreateMap<Dto, SubEntity>();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MapperConfig.Initialize();

            var dto = new Dto {Property = "Hello", SubProperty = "World"};
            var subEntity = new SubEntity {SubProperty = "Universe"};
            var entity = new Entity {Property = "Good bye", Sub = subEntity};

            Mapper.Map(dto, entity);

            Console.WriteLine(string.Format("entity.Property == {0}, entity.Sub.SubProperty == {1}",
                entity.Property, entity.Sub.SubProperty));
            Console.WriteLine(string.Format("entity.Sub == subEntity: {0}", 
                entity.Sub == subEntity));

        }
    }
}

If you run the example, which is using MapperConfig, you'll get the following output:

entity.Property == Hello, entity.Sub.SubProperty == World
entity.Sub == subEntity: False

The string properties all get updated as one would want them to, but entity.Sub gets replaced with a new instance of SubEntity which is no good for when you are wanting to update entities for an ORM that will be persisted to a database.

If you modify Main so that MapperConfig2 is used instead, you'll still have the string properties updated as before, but, entity.sub still has the same instance of SubEntity that it had before. Running the example with MapperConfig2 gives this output:

entity.Property == Hello, entity.Sub.SubProperty == World
entity.Sub == subEntity: True

The key difference in MapperConfig2 is that ResolveUsing is used along with MyResolver to preserve the value of entity.Sub.

like image 180
John Mills Avatar answered Sep 28 '22 03:09

John Mills