Using AutoMapper to unflatten a DTO

前端 未结 7 906
执念已碎
执念已碎 2020-11-28 07:52

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 beginni

相关标签:
7条回答
  • 2020-11-28 08:27

    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)));
    
    0 讨论(0)
  • 2020-11-28 08:27

    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...

    0 讨论(0)
  • 2020-11-28 08:36

    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.

    0 讨论(0)
  • 2020-11-28 08:36

    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 }; }))
    
    0 讨论(0)
  • 2020-11-28 08:36

    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 }; }))
    
    0 讨论(0)
  • 2020-11-28 08:43

    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!

    0 讨论(0)
提交回复
热议问题