AutoMapper Multiple Classes in One Class ViewModel

2019-05-25 21:31发布

问题:

I've got classes like this

public class ClassOne
{
    public string Id { get; set; }

    //...many others properties

} 

public class ClassTwo
{
    public string Id { get; set; }

    //...many others properties differents from classeOne

    public string Email { get; set; }
} 

public class ClassThree
{
    public string Id { get; set; }

    //...many others properties differents from classeOne and classTwo

    public string Email { get; set; }
} 


public class ViewModel
{
    //...same properties as classeOne

    public string EmailClasseTwo { get; set; }
    public string EmailClassThree { get; set; }
} 

public class ObjectReturnByLinqQuery
{
   public ClassOne classOne { get; set; }    
   public ClassTwo classTwo { get; set; }
   public ClassThree classThree { get; set; }
} 

How to create map between the ViewModel and ObjectReturnByLinqQuery without doing one by one member for each classes ?

something like

config.CreateMap<ObjectReturnByLinqQuery, ViewModel>()
.ForMember(ViewModel => ViewModel.EmailClasseTwo , ModelDB => ModelDB.MapFrom(src => src.ClasseTwo.Email))
.ForMember(ViewModel => ViewModel.EmailClasseThree , ModelDB => ModelDB.MapFrom(src => src.ClasseThree.Email))

and for all other members take them from src.ClassOne

回答1:

Unfortunately there is no out of the box way to do that. But you can create a Custom Naming Convention like this:

using AutoMapper;
using AutoMapper.Configuration.Conventions;
using System.Reflection;

class CustomMapMember : IChildMemberConfiguration
{
    Dictionary<TypePair, List<PropertyInfo>> map = new Dictionary<TypePair, List<PropertyInfo>>();

    public CustomMapMember Add(Type destType, Type sourceType, string sourcePropertyName)
    {
        var key = new TypePair(sourceType, destType);
        if (!map.TryGetValue(key, out var properties))
            map.Add(key, properties = new List<PropertyInfo>());
        properties.Add(sourceType.GetProperty(sourcePropertyName));
        return this;
    }

    public bool MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceType, Type destType, Type destMemberType, string nameToSearch, LinkedList<MemberInfo> resolvers, IMemberConfiguration parent)
    {
        if (map.TryGetValue(new TypePair(sourceType.Type, destType), out var properties))
        { 
            foreach (var property in properties)
            {
                resolvers.AddLast(property);
                var memberType = options.CreateTypeDetails(property.PropertyType);
                if (parent.MapDestinationPropertyToSource(options, memberType, destType, destMemberType, nameToSearch, resolvers))
                    return true;
                resolvers.RemoveLast();
            }
        }
        return false;
    }
}

and then use it like this:

config.AddMemberConfiguration().AddMember<CustomMapMember>(m =>
    // Configure the custom mappings here
    m.Add(typeof(ViewModel), typeof(ObjectReturnByLinqQuery), nameof(ObjectReturnByLinqQuery.classOne))
);

config.CreateMap<ObjectReturnByLinqQuery, ViewModel>()
    .ForMember(ViewModel => ViewModel.EmailClasseTwo, ModelDB => ModelDB.MapFrom(src => src.classTwo.Email))
    .ForMember(ViewModel => ViewModel.EmailClassThree, ModelDB => ModelDB.MapFrom(src => src.classThree.Email));