AutoMapper convert from multiple sources
Asked Answered
S

9

100

Let's say I have two model classes:

public class People {
   public string FirstName {get;set;}
   public string LastName {get;set;}
}

Also have a class Phone:

public class Phone {
   public string Number {get;set;}
}

And I want to convert to a PeoplePhoneDto like this:

public class PeoplePhoneDto {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public string PhoneNumber {get;set;}
}

Let's say in my controller I have:

var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);

// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;

Is this possible ?

Stenopetalous answered 28/1, 2014 at 18:2 Comment(4)
@Andrei while I do agree it seems similar, it is a difference in the problem it's trying to solve. also it's hard to understand from that question how it would apply to this one.Stenopetalous
Why not make PeoplePhoneDto have a People and Phone member?Skateboard
Because that's not what I want to expose.Stenopetalous
Voting to reopen - while I do think that #12429710 is a duplicate, it (along with its one answer) seem a bit too localized to be considered canonical. There is precedent for duplicate questions not counting if they weren't answered well enough to settle the matter.Patentee
C
31

Try this if you're using C# 7+ (a slight variation of @Paweł Bejgerthat's answer that will make it even simpler):

Mapper.CreateMap<(People people, Phone phone), PeoplePhoneDto>()
    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.people.FirstName))
    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.people.LastName))
    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.phone.Number ));

And then use it like this:

var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>((people, phone));

And yes, you will need a couple of brackets around the arguments, it's not a mistake. The reason behind it is that you're passing one single source (not two) which happens to be a (People, Phone) tuple.

Credence answered 29/3, 2021 at 20:22 Comment(6)
This helped me. There is no need for additional extension methods. Very simple.Retinoscope
@luis it looks like this should work, but for me all my members are coming through null. Is this still working for you?Chatelaine
found my problem- my view didn't have setters :facepalm:Chatelaine
I use this a lot! Often with the actual mapping being a ConvertUsing, picking fields from two or more sources. Sure, you could make a separate "manual" mapping method for it instead of using AutoMapper, but it comes down to consistency for me - if I use AutoMapper elsewhere it's nice to use it for this too.Segovia
Anyway, one downside to this is in the usage - due to it not being type safe, it's easy to get the tuple wrong. One option to get type safety in the usage is to define an extension method: public static PeoplePhoneDto MapPeoplePhone(this IMapper mapper, People people, Phone phone) => mapper.Map<PeoplePhoneDto>((people, phone)); Then it's just a problem of coming up with good extension method names, but we all love naming right? ;)Segovia
Potentially, you may also use IncludeMembers to save from writing out explicit mappings. For example Mapper.CreateMap<(People people, Phone phone), PeoplePhoneDto>().IncludeMembers(x => x.People, x => x.Phone).Frankhouse
A
125

You cannot directly map many sources to single destination - you should apply maps one by one, as described in Andrew Whitaker answer. So, you have to define all mappings:

Mapper.CreateMap<People, PeoplePhoneDto>();
Mapper.CreateMap<Phone, PeoplePhoneDto>()
        .ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));

Then create destination object by any of these mappings, and apply other mappings to created object. And this step can be simplified with very simple extension method:

public static TDestination Map<TSource, TDestination>(
    this TDestination destination, TSource source)
{
    return Mapper.Map(source, destination);
}

Usage is very simple:

var dto = Mapper.Map<PeoplePhoneDto>(people)
                .Map(phone);
Alamo answered 28/1, 2014 at 18:28 Comment(7)
There is an abstraction IMapper over AutoMapper to map several sources into single destination that I use.Civic
@Sergey Berezovskiy, I created the mappings, added the extension method in PeoplePhoneDto class, and copy-pasted your usage (I.e., I copy-pasted everything needed), but I get a "No overload for method Map takes 1 argument" error. What am I missing? I'm using Automapper 4.2.1.Stratification
@HeyJude make sure that your Map extension method is visible at the point you do the mapping (i.e. correct using directive is added)Alamo
This is good, but I don't like to use static Map due to not being able to mock it, so I will try ilyas Imapper abstractionCinchonine
Will this create the DTO Class 2 times for each map or just once?Bulldoze
Extension method didn't work for me (AutoMapper v9 Mapper.Map is not static) Alternative is just using your mapper instance e.g var foo = mapper.Map<PeoplePhoneDto>(people); mapper.Map(phone, foo);Ten
@Ten Extention Method works but in a different way, I am also using .net core & AutoMapper 10, I had the same issue, none of the recommended solutions was a working one for my case, so I created my own way and shared it with you guys if you are still having the same problem please check my answer.Poetics
A
35

You could use a Tuple for this:

Mapper.CreateMap<Tuple<People, Phone>, PeoplePhoneDto>()
    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.Item1.FirstName))
    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.Item1.LastName))
    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.Item2.Number ));

In case you would have more source models you can use a different representation (List, Dictionary or something else) that will gather all these models together as a source.

The above code should preferaby be placed in some AutoMapperConfiguration file, set once and globally and then used when applicable.

AutoMapper by default supports only a single data source. So there is no possibility to set directly multiple sources (without wrapping it up in a collection) because then how would we know what in case if for example two source models have properties with the same names?

There is though some workaround to achieve this:

public static class EntityMapper
{
    public static T Map<T>(params object[] sources) where T : class
    {
        if (!sources.Any())
        {
            return default(T);
        }

        var initialSource = sources[0];

        var mappingResult = Map<T>(initialSource);

        // Now map the remaining source objects
        if (sources.Count() > 1)
        {
            Map(mappingResult, sources.Skip(1).ToArray());
        }

        return mappingResult;
    }

    private static void Map(object destination, params object[] sources)
    {
        if (!sources.Any())
        {
            return;
        }

        var destinationType = destination.GetType();

        foreach (var source in sources)
        {
            var sourceType = source.GetType();
            Mapper.Map(source, destination, sourceType, destinationType);
        }
    }

    private static T Map<T>(object source) where T : class
    {
        var destinationType = typeof(T);
        var sourceType = source.GetType();

        var mappingResult = Mapper.Map(source, sourceType, destinationType);

        return mappingResult as T;
    }
}

And then:

var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);

But to be quite honest, even though I am using AutoMapper for already a few years I have never had a need to use mapping from multiple sources. In cases when for example I needed multiple business models in my single view model I simply embedded these models within the view model class.

So in your case it would look like this:

public class PeoplePhoneDto {
    public People People { get; set; }
    public Phone Phone { get; set; }
}
Abbreviated answered 28/1, 2014 at 18:4 Comment(6)
So I must create a tuple before doing the mapping, I'm wondering what are the real benefits of automapper... sound a little overkill. Is there any way to avoid creating another type (tuple, dic, etc) ?Stenopetalous
thanks for your answer, it leads me to understand a lot about automapper. Thing is, when you expose API you carefully models data in ways that sometimes having embedded models are not desired because you transfer your 'domain-related' problems to a consumer and I'm trying to making it easy for clients to consume without nested types. If it were for my own internal use, I will go forwards with the embedded option for sure.Stenopetalous
The PeoplePhoneDto you suggested looks good, but I still think mapping from multiple sources is useful, most notably in mapping view models. I think most real world scenarios require multiple sources to construct a view model. I suppose you could create view models that aren't flattened to get around the issue, but I think it's a good idea to create the view models without caring what the business schema looks like.Degroot
Also does automapper care what order the types are in in the tuple? is Tuple<People, Phone> the same as Tuple<Phone, People>?Degroot
@TheMuffinMan Tuple exposes the first type argument as Item1, the second as Item2, etc. In that sense, the order matters.Inman
Regarding you never having to map from multiple sources: this will be an issue everytime you have a source with nested/multiple objects which need to be flattened in the destination, making this sceniario pretty common I would say (unless you use the same classes across different layers, which I wouldn't recommend doing)Municipal
C
31

Try this if you're using C# 7+ (a slight variation of @Paweł Bejgerthat's answer that will make it even simpler):

Mapper.CreateMap<(People people, Phone phone), PeoplePhoneDto>()
    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.people.FirstName))
    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.people.LastName))
    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.phone.Number ));

And then use it like this:

var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>((people, phone));

And yes, you will need a couple of brackets around the arguments, it's not a mistake. The reason behind it is that you're passing one single source (not two) which happens to be a (People, Phone) tuple.

Credence answered 29/3, 2021 at 20:22 Comment(6)
This helped me. There is no need for additional extension methods. Very simple.Retinoscope
@luis it looks like this should work, but for me all my members are coming through null. Is this still working for you?Chatelaine
found my problem- my view didn't have setters :facepalm:Chatelaine
I use this a lot! Often with the actual mapping being a ConvertUsing, picking fields from two or more sources. Sure, you could make a separate "manual" mapping method for it instead of using AutoMapper, but it comes down to consistency for me - if I use AutoMapper elsewhere it's nice to use it for this too.Segovia
Anyway, one downside to this is in the usage - due to it not being type safe, it's easy to get the tuple wrong. One option to get type safety in the usage is to define an extension method: public static PeoplePhoneDto MapPeoplePhone(this IMapper mapper, People people, Phone phone) => mapper.Map<PeoplePhoneDto>((people, phone)); Then it's just a problem of coming up with good extension method names, but we all love naming right? ;)Segovia
Potentially, you may also use IncludeMembers to save from writing out explicit mappings. For example Mapper.CreateMap<(People people, Phone phone), PeoplePhoneDto>().IncludeMembers(x => x.People, x => x.Phone).Frankhouse
M
6

I'd write an extension method as below:

    public static TDestination Map<TSource1, TSource2, TDestination>(
        this IMapper mapper, TSource1 source1, TSource2 source2)
    {
        var destination = mapper.Map<TSource1, TDestination>(source1);
        return mapper.Map(source2, destination);
    }

Then usage would be:

    mapper.Map<People, Phone, PeoplePhoneDto>(people, phone);
Malave answered 23/3, 2020 at 21:43 Comment(0)
P
4

perhaps it sounds to be an old post but there might be some guys still struggling with the same issue, referring to AutoMapper IMapper Map function documentation, we can reuse the same existing destination object for mapping from a new source, provided that you already created a map for each source to destination in profile, then you can use this simple Extention method:

 public static class MappingExtentions
    {
        public static TDestination Map<TDestination>(this IMapper mapper, params object[] sources) where TDestination : new()
        {
            return Map(mapper, new TDestination(), sources);
        }

        public static TDestination Map<TDestination>(this IMapper mapper, TDestination destination, params object[] sources) where TDestination : new()
        {
            if (!sources.Any())
                return destination;

            foreach (var src in sources)
                destination = mapper.Map(src, destination);

            return destination;
        }
    }

please note that I have created a constraint for destination type which says it must be an instantiate-able type. if your type is not like that use default(TDestination) instead of new TDestination().

Warning: this type of mapping is a bit dangerous sometimes because the destination mapping properties might be overwritten by multiple sources and tracing the issue in larger apps can be a headache, there is a loose workaround you can apply, you can do as below, but again it is not a solid solution at all:

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string PhoneNumber { get; set; }
    }

    public class Contact
    {
        public string Address { get; set; }
        public string PhoneNumber { get; set; }
        public string Other{ get; set; }
    }


    public class PersonContact
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address{ get; set; }
        public string PhoneNumber { get; set; }
    }

    public class PersonMappingProfile : MappingProfile
    {
        public PersonMappingProfile()
        {
            this.CreateMap<Person, PersonContact>();

            
            this.CreateMap<Phone, PersonContact>()
                .ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom((src, dest) => dest.PhoneNumber ?? src.PhoneNumber)) // apply mapping from source only if the phone number is not already there, this way you prevent overwriting already initialized props
                .ForAllOtherMembers(o => o.Ignore());
        }
    }
Poetics answered 28/8, 2020 at 9:2 Comment(1)
Could you give us an example of usage?Islander
E
3

Using FluentAPI style for better discoverability and guidance usage.

 public static class MapperExtensions
    {
        public static IMultiMapBuilder<TDestination> StartMultiMap<TDestination>(this IMapper mapper, object source)
        {
            return new MultiMapBuilder<TDestination>(mapper, source);
        }
    }

    public interface IMultiMapBuilder<T>
    {
        IMultiMapBuilder<T> Then<TSource>(TSource source);
        T Map();
    }

    public class MultiMapBuilder<T> : IMultiMapBuilder<T>
    {
        private readonly IMapper _mapper;
        private readonly T _mappedObject;
        public MultiMapBuilder(IMapper mapper, object source)
        {
            _mapper = mapper;
            _mappedObject = mapper.Map<T>(source);
        }
        public IMultiMapBuilder<T> Then<TSource>(TSource source)
        {
            _mapper.Map(source, _mappedObject);
            return this;
        }

        public T Map()
        {
            return _mappedObject;
        }
    }

Sample Usage:

//-- By IMapper Extension
var mapped = _mapper.StartMultiMap<SomeType>(source1)
     .Then(source2)
     .Then(source3)
     .Map();

or 

//-- new instance of MultiMapBuilder
var mapped = new MultiMapBuilder<SomeType>(_mapper, source1)
     .Then(source2)
     .Then(source3)
     .Map();


Escalera answered 9/8, 2021 at 23:16 Comment(0)
D
2

There is a breaking change in AutoMapper 9.0 that no longer provides an API for static Mapper. So, we need to use an instance now. For those using the newer versions, an extension method that uses inferred types with/without a destination object follow:

public static class AutoMapperExtensions
{
    public static TDestination Map<TDestination>(this IMapper mapper, params object[] source) where TDestination : class
    {
      TDestination destination = mapper.Map<TDestination>(source.FirstOrDefault());

      foreach (var src in source.Skip(1))
        destination = mapper.Map(src, destination);

      return destination;
    }

    public static TDestination Map<TDestination>(this IMapper mapper, TDestination destination, params object[] source) where TDestination : class
    {
      foreach (var src in source)
        destination = mapper.Map(src, destination);

      return destination;
    }
}
Dhow answered 23/7, 2021 at 19:47 Comment(0)
J
0

If you have a scenario when destination type should be mapped from one of sources and you want to use linq projections, you can do following.

    Mapper.CreateMap<People, PeoplePhoneDto>(MemberList.Source);
    Mapper.CreateMap<Phone, PeoplePhoneDto>(MemberList.Source)
          .ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));

    CreateMap<PeoplePhoneDto,(People,Phone)>(MemberList.Destination)
           .ForMember(x => x.Item1, opts => opts.MapFrom(x => x))
           .ForMember(x => x.Item2, opts => opts.MapFrom(x => x.PhoneNumber))
           .ReverseMap();

I needed this mostly for cross apply queries like this.

       var dbQuery =
          from p in _context.People
          from ph in _context.Phones
             .Where(x => ...).Take(1)
          select ValueTuple.Create(p, ph);
       var list = await dbQuery
          .ProjectTo<PeoplePhoneDto>(_mapper.ConfigurationProvider)
          .ToListAsync();
Jump answered 12/9, 2019 at 20:36 Comment(0)
Q
0

There's lots of options already provided, but none of them really fit what I wanted. I was falling asleep last night and had the thought:

Lets say you want to map your two classes, People and Phone to PeoplePhoneDto

public class People {
   public string FirstName {get;set;}
   public string LastName {get;set;}
}

+

public class Phone {
   public string Number {get;set;}
}

=

public class PeoplePhoneDto {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public string PhoneNumber {get;set;}
}

All you really need is another wrapper class for Automapper purposes.

public class PeoplePhone {
    public People People {get;set;}
    public Phone Phone {get;set;}
}

And then define the mapping:

CreateMap<PeoplePhone, PeoplePhoneDto>()

And use it

var dto = Map<PeoplePhoneDto>(new PeoplePhone
{
    People = people,
    Phone = phone,
});
Quadri answered 30/5, 2020 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.