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 beginning to wonder if AutoMapper migh开发者_开发问答t be the wrong tool for the job.
Consider this example of domain objects (one entity and one value):
public class Person
{
public string Name { get; set; }
public StreetAddress Address { get; set; }
}
public class StreetAddress
{
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
My DTO (from a Linq-to-SQL object) is coming out looking roughly like this:
public class PersonDTO
{
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
}
I'd like to be able to do this in my repository:
return Mapper.Map<PersonDTO, Person>(result);
I've tried configuring AutoMapper every way I can figure, but I keep getting the generic Missing type map configuration or unsupported mapping error, with no details to tell me where I'm failing.
I've tried a number of different configurations, but here are a few:
Mapper.CreateMap<PersonDTO, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(Mapper.Map<Person, Domain.StreetAddress>));
and
Mapper.CreateMap<Person, Domain.Person>()
.ForMember(dest => dest.Address.Address1, opt => opt.MapFrom(src => src.Address))
.ForMember(dest => dest.Address.City, opt => opt.MapFrom(src => src.City))
.ForMember(dest => dest.Address.State, opt => opt.MapFrom(src => src.State));
I've read that flattening objects with AutoMapper is easy, but unflattening them isn't easy...or even possible. Can anyone tell me whether I'm trying to do the impossible, and if not what I'm doing wrong?
Note that my actual objects are a little more complicated, so it's possible I'm leaving out info that is the key to the error...if what I'm doing looks right I can provide more info or start simplifying my objects for testing.
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.
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 }; }))
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)));
use https://github.com/omuleanu/ValueInjecter, it does flattening and unflattening, and anything else you need, there is an asp.net mvc sample application in the download where all the features are demonstrated (also unit tests)
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 }; }))
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!
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...
精彩评论