92

I am using auto mapper to map multiple objects (db class into ui objects).

Map 1:

Mapper.CreateMap<sourceone, destination>().ForMember(sss => sss.one, m => m.MapFrom(source => source.abc));

Map 2:

Mapper.CreateMap<sourcetwo, destination>().ForMember(sss => sss.two, m => m.MapFrom(source => source.xyz));

destination d = new destination();

//Map 1

d = AutoMapper.Mapper.Map<sourceone, destination>(sourceone);

//Map 2

d = AutoMapper.Mapper.Map<sourcetwo, destination>(sourcetwo);

Once I make call to the 'Map 2', the values that are populated using Map 1 are lost.. (i.e destination.one is becoming empty). How do I fix this?

tereško
  • 58,060
  • 25
  • 98
  • 150
CoolArchTek
  • 3,729
  • 12
  • 47
  • 76

7 Answers7

108

Map has an overload that takes a source and destination object:

d = AutoMapper.Mapper.Map<sourceone, destination>(sourceone);

/* Pass the created destination to the second map call: */
AutoMapper.Mapper.Map<sourcetwo, destination>(sourcetwo, d);
Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
  • 22
    this is ugly and confusing, the API should clearly include a Map(params) method – Filip Mar 09 '16 at 11:36
  • 1
    @Flip: I could be misunderstanding what you mean, but it _does_ include a `Map(params)` method, it just returns a new `T`. – Andrew Whitaker Mar 09 '16 at 14:42
  • @Filip sounds like this would be a simple extension to add if that's how you prefer the API to look – Sonic Soul Nov 01 '17 at 20:34
  • This no longer compiles on newer AutoMappers, but the same principle applies: Use the instance of mapper instead of the static method. – Jeramy Rutley Sep 18 '20 at 21:07
  • this doesn't allow to create 1 one model item from 2 DTOs because only parametrized constructor can be the entry point to pass properties from 2 dto. And model's properties must be protected to set, but public for get. So, once you pass params from the 1st dto, there is no way to pass params from 2nd dto and don't get argument null exception (for params of 2nd dto) - no way to keep consistent model – Artem A Sep 02 '22 at 22:12
66
mapper.MergeInto<PersonCar>(person, car)

with the accepted answer as extension-methods, simple and general version:

public static TResult MergeInto<TResult>(this IMapper mapper, object item1, object item2)
{
    return mapper.Map(item2, mapper.Map<TResult>(item1));
}

public static TResult MergeInto<TResult>(this IMapper mapper, params object[] objects)
{
    var res = mapper.Map<TResult>(objects.First());
    return objects.Skip(1).Aggregate(res, (r, obj) => mapper.Map(obj, r));
}

after configuring mapping for each input-type:

IMapper mapper = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonCar>();
    cfg.CreateMap<Car, PersonCar>();
}).CreateMapper();
Grastveit
  • 15,770
  • 3
  • 27
  • 36
9
public class Person
{
    public string Name { get; set; }
    public string PhNo { get; set; }
}
public class Company
{
    public int EmpNo { get; set; }
    public string Title { get; set; }
}

public class PersonCompany
{
    public string Name { get; set; }
    public string PhNo { get; set; }

    public int EmpNo { get; set; }
    public string Title { get; set; }
}

//you can test as below
        var pMap = Mapper.CreateMap<Person,PersonCompany>();
        pMap.ForAllMembers(d => d.Ignore()); 
        pMap.ForMember(d => d.Name, opt => opt.MapFrom(s => s.Name))
            .ForMember(d => d.PhNo, opt => opt.MapFrom(s => s.PhNo));

        var cMap = Mapper.CreateMap<Company, PersonCompany>();
        cMap.ForAllMembers(d => d.Ignore());
        cMap.ForMember(d => d.EmpNo, opt => opt.MapFrom(s => s.EmpNo))
            .ForMember(d => d.Title, opt => opt.MapFrom(s => s.Title));


        var person = new Person { Name = "PersonName", PhNo = "212-000-0000" };
        var personCompany = Mapper.Map<Person,PersonCompany>(person);
        var company = new Company { Title = "Associate Director", EmpNo = 10001 };
        personCompany = Mapper.Map(company, personCompany);

        Console.WriteLine("personCompany.Name={0}", personCompany.Name);
        Console.WriteLine("personCompany.PhNo={0}", personCompany.PhNo);
        Console.WriteLine("personCompany.EmpNo={0}", personCompany.EmpNo);
        Console.WriteLine("personCompany.Title={0}", personCompany.Title);
Ramakrishna Talla
  • 1,011
  • 12
  • 7
7

According to me you should avoid calling the overloaded Map method taking an instance of the destination object as explained in the accepted answer. This won't let you test/validate your mapping configuration (Mapper.Configuration.AssertConfigurationIsValid()) or to do so you will add a lot of 'Ignore' in your mappings.

A very simple solution is to create a composite type holding source references and define your mapping to the destination based on that composite type.

Something like:

    public class SourceOneTwo
    {
        public SourceOne SourceOne { get; set; }
        public SourceTwo SourceTwo { get; set; }
    }
    static void Main(string[] args)
    {
        var config = new MapperConfiguration(cfg => 
            cfg.CreateMap<SourceOneTwo, Destination>()
            .ForMember(dest => dest.one, m => m.MapFrom(source => source.SourceOne.abc))
            .ForMember(dest => dest.two, m => m.MapFrom(source => source.SourceTwo.xyz)));
        config.AssertConfigurationIsValid();
    }
asidis
  • 1,374
  • 14
  • 24
6

Just wanted to add, that these days you can make use of tuple syntax to define mapping from multiple types.

// configuring
Mapper
  .CreateMap<(SourceType1 Source1, SourceType2 Source2), DestinationType>()
    .ForMember(sss => sss.one, m => m.MapFrom(source => source.Source1.abc))
    .ForMember(sss => sss.two, m => m.MapFrom(source => source.Source2.xyz));

// using
var result = Mapper.Map<DestinationType>((source1, source2));

Pros:

  • don't have to remember to map from the second type after mapping from the first
  • all mapping is defined at once and in one place (as long as you don't need separate mappings as well as a combined one)
  • less of a clumber when mapping from more than 2 source types
  • essentially the @asidis's idea, but does not require a separate type

Cons:

  • well, as with Automapper in general, you have to know where to look, to find out what types to use for mapping
  • may look be a bit unwieldy when a number of types is involved
Korli
  • 478
  • 4
  • 9
  • Looks like this is the only one correct way to create SOLID and DDD acceptable models, passing params via constructus => without code smelling public setters of the model class and so. – Artem A Sep 02 '22 at 22:16
5

Nowadays it looks like that:

DestinationDto = _mapper.Map(source2, _mapper.Map<source1type, destinationType>(source1));
Tropin Alexey
  • 606
  • 1
  • 7
  • 16
  • This is really helpful! Thank you. I was heading down a path that looked like a maintenance nightmare waiting to happen. I had: (myObject1, myObject2) => { var _combinedObject = _mapper.Map(myObject2); _combinedObject.someProperty = myObject1.SomeProperty; _combinedObject.anotherProperty= myObject1.AnotherProperty; return _combinedObject ; }) .ToList(); Now it's much shorter and easier to read IMO: (myObject1, myObject2) => { return _mapper.Map(myObject2, _mapper.Map(myObject1)); }) .ToList(); – godfathr May 27 '21 at 16:58
0

Solution via an extension:

public static class MapperExtension
{
    public static T MergeInto<T>(this IMapper mapper, params object[] sources)
    {
        return (T)sources
            .Aggregate(Activator.CreateInstance(typeof(T)), (dest, source) => mapper.Map(source, dest));
    }
}

Usage:

_mapper.MergeInto<TypeOut>(new object[] { type1, type2, type3 });

Notes:

Your ITypeConverter should support not only returning a new type, but also for a destination that already was created.

Sample:

public class TypeInToTypeOut : ITypeConverter<TypeIn, TypeOut>
{
    public TypeOut Convert(TypeIn source, TypeOut destination, ResolutionContext context)
    {
        var response = destination ?? new();

        response.Field1 = source?.FielderA;
        response.Field2 = source?.FielderB;

        return response;
    }
}
cuasiJoe
  • 1,089
  • 10
  • 12