Automapper merging objects issue - c#

After getting automapper to work (previous question), I'm struggling with another problem (took it to another question, so the first one wouldn't be too complicated)...
I have next classes:
public class Model1
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDay { get; set; }
public int Gender { get; set; }
public string NickName { get; set; }
}
public class Model2
{
public bool Married { get; set; }
public int Children { get; set; }
public bool HasPet { get; set; }
}
public class Entity1
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDay { get; set; }
public int Gender { get; set; }
}
public class Entity2
{
public bool Married { get; set; }
public int Children { get; set; }
public bool HasPet { get; set; }
public string NickName { get; set; }
}
These objects are schematically similar to my original objects, except to the name and the complexity.
And AutoMapper configuration class (called from Global.asax):
public class AutoMapperConfig
{
public static MapperConfiguration MapperConfiguration { get; set; }
public static void Configure()
{
MapperConfiguration = new MapperConfiguration(cfg => {
cfg.AddProfile<Out>();
cfg.CreateMap<SuperModel, SuperEntity>();
});
MapperConfiguration.AssertConfigurationIsValid();
}
}
public class Out: Profile
{
protected override void Configure()
{
CreateMap<Model1, Entity1>();
CreateMap<Model2, Entity2>()
.ForMember(dest => dest.NickName, opt => opt.Ignore());
CreateMap<Model1, Entity2>()
.ForMember(dest => dest.Married, opt => opt.Ignore())
.ForMember(dest => dest.Children, opt => opt.Ignore())
.ForMember(dest => dest.HasPet, opt => opt.Ignore());
CreateMap<SuperModel, SuperEntity>()
.ForMember(dest => dest.Entity1, opt => opt.MapFrom(src => src.Model1))
.ForMember(dest => dest.Entity2, opt => opt.MapFrom(src => src.Model2));
}
}
When I need the object to be converted, I do next (at this point I have _superModel initialized and filled with data):
SuperEntity _superEntity = new SuperEntity();
AutoMapperConfig.MapperConfiguration.CreateMapper().Map<SuperModel, SuperEntity>(_superModel, _superEntity);
So, I map Model1 to Entity1 (witch is fine), and also Model2 to Entity2 (witch is also fine, except the Id property, which is ignored).
Main objects SuperModel and SuperEntity are mapped as well, and seems to work fine.
The problem happens, when I map Model1 to Entity2, to get the NickName (thought the rest of the properties are ignored). Some how It is always null!
Any ideas?

The problem is you want to map a destination property (Entity2) from multiples source property values (Children, Married and HasPet from Model2 and Nickname from Model1).
You can solve your situation using an Custom Resolver or with AfterMap method.
Using Custom Resolvers:
You have to create a class inhering from ValueResolver to define how you will map Entity2 from an SuperModel:
public class CustomResolver : ValueResolver<SuperModel, Entity2>
{
protected override Entity2 ResolveCore(SuperModel source)
{
return new Entity2
{
Children = source.Model2.Children,
HasPet = source.Model2.HasPet,
Married = source.Model2.Married,
NickName = source.Model1.NickName
};
}
}
Then, use it like this:
CreateMap<SuperModel, SuperEntity>()
.ForMember(dest => dest.Entity1, opt => opt.MapFrom(src => src.Model1))
.ForMember(dest => dest.Entity2, opt => opt.ResolveUsing<CustomResolver>());
Using AfterMap:
You can execute actions after the mapping, so pass the value from Model1.Nickname to Entity2.Nickname after the map:
CreateMap<SuperModel, SuperEntity>()
.ForMember(dest => dest.Entity1, opt => opt.MapFrom(src => src.Model1))
.ForMember(dest => dest.Entity2, opt => opt.MapFrom(src => src.Model2))
.AfterMap((m, e) => e.Entity2.NickName = m.Model1.NickName);

Related

How to map (using AutoMapper) a list in a many to many entity

I want to map ProjectDto object to Project object.
So, ProjectDto class contains list of styles:
public class ProjectDto
{
public List<StyleDto>? Styles { get; set; }
// and other properties...
}
And it's a Project class:
public class Project
{
public virtual IEnumerable<StyleOfProject> StylesOfProject { get; set; }
// and other properties...
}
There is many-to-many relationship between Style and Project, which is represented in StyleOfProject class:
public class StyleOfProject
{
public int ProjectId { get; set; }
public virtual Project Project { get; set; }
public int StyleId { get; set; }
public virtual Style Style { get; set; }
}
public class Style
{
public virtual IEnumerable<StyleOfProject> StyleOfProjects { get; set; }
// and other properties...
}
So, I tried to map like this:
CreateMap<ProjectDto, Project>().ForMember(dest => dest.StylesOfProject, opt => opt.MapFrom(src => src.Styles))
And I got empty StylesOfProject. I understand this is incorrect mapping way, but I don't have any right ideas how to map it.
So, I found a solution to my problem:
CreateMap<ProjectDto, Project>()
.ForMember(dest => dest.StylesOfProject,
opt => opt.MapFrom(src => src.Styles))
.AfterMap((_, dest) =>
{
foreach (var s in dest.StylesOfProject)
{
s.ProjectId = dest.Id;
s.Project = dest;
}
});
CreateMap<StyleDto, StyleOfProject>()
.ForMember(dest => dest.Style,
opt => opt.MapFrom(src => src))
.AfterMap((_, dest) =>
{
dest.StyleId = dest.Style.Id;
});
CreateMap<StyleDto, Style>();
AfterMap() is very useful thing in AutoMapper. You can find more information on this website.

Automapper Custom configuration for members is only supported for top-level individual members on a type

I am trying to map a result I get from database and I have the following models
public class ClientTest
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClientTestDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClientDbItem
{
public ClientTest Client { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Value { get; set; }
}
and the following mapping
CreateMap<ClientTest, ClientTestDto>();
CreateMap<ClientDbItem, ClientTestDto>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.Client));
When I run the software I get
Custom configuration for members is only supported for top-level
individual members on a type
Why is that happening If I am creating the config for ClientTest and ClientTestDto first?
There's a special API for that, IncludeMembers. See here.
This error might also arise when you try to map the top-level element to a constructor:
.ForMember(dest => dest, opt => opt.MapFrom(
src => new B1(src.Prop3, src.Prop4)));
Instead of mapping the top-level element MEMBER to the constructor (which works fine):
.ForMember(dest => dest.MyProperty, opt => opt.MapFrom(
src => new B1(src.Prop3, src.Prop4)));
To avoid this, use .ForCtorParam
Example:
CreateMap<Source, DestinationWithConstructor>()
.ForCtorParam("code", opt => opt.MapFrom(src => src.Prop3))
.ForCtorParam("text", opt => opt.MapFrom(src => src.Prop4))

Mapping nested objects where nested different types have different property names in AutoMapper

I am trying to map one nested object to another like the below example. I can easily map BobAge to JimAge etc. But how do I map Bobs Collection to Jims collection. This can go n levels deep. Thanks very much
public class Bob
{
public int BobAge { get; set; }
public string BobName { get; set; }
public List<Bob> Bobs { get; set; }
}
public class Jim
{
public int JimAge { get; set; }
public string JimName { get; set; }
public List<Jim> Jims { get; set; }
}
cfg.CreateMap<Bob, Jim>()
.ForMember(d => d.JimAge, opt => opt.MapFrom(src => src.BobAge))
.ForMember(d => d.JimName, opt => opt.MapFrom(src => src.BobName))
.ForMember(d => d.Jims, opt => opt.MapFrom(src => src.Bobs));

AutoMapper - subObject stay null when mapping back to DTO from ViewModel

I have this map:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Sistema.DataEntities.Models.Cliente, CadastroModule.Model.Cliente>().ReverseMap();
});
CadastroModule.Model.Cliente (MVVM MODEL):
public class Cliente
{
public int ClienteID { get; set; }
public string Nome { get; set; }
public string EnderecoCEP { get; set;}
public string EnderecoBairro { get; set; }
public string EnderecoLogradouro { get; set; }
public int EnderecoNumero { get; set; }
public string EnderecoComplemento { get; set; }
}
Sistema.DataEntities.Models (POCO):
public class Cliente : Entity
{
public Cliente()
{
Endereco = new Endereco();
}
public int ClienteID { get; set; }
public string Nome { get; set; }
public Endereco Endereco { get; set; }//1 pra 1 (Complex type EF)
}
Sistema.DataEntities.Models (POCO):
public class Endereco : Entity
{
public string CEP { get; set; }
public string Bairro { get; set; }
public string Logradouro { get; set; }
public int Numero { get; set; }
public string Complemento { get; set; }
}
When i try this its run perfectly:
var t = _clienteService.ClienteService_GetAll().Project().To<Cliente>();
But when i need to back to poco i get trouble:
Sistema.DataEntities.Models.Cliente clifinal = Mapper.Map<Cliente, Sistema.DataEntities.Models.Cliente>(ObCliente);
My result:
Why the Endereco object is with null values?
Automapper isn't built as much for "unflattening" objects. Your Model --> ViewModel mapping works by convention; the "Endereco" prefix in the destination tells AutoMapper to look in a nested object.
To get this to work the other way, you'll have to add a mapping manually from CadastroModule.Model.Cliente to Sistema.DataEntities.Models.Endereco and then use that mapping in the reverse mapping from CadastroModule.Model.Cliente to Sistema.DataEntities.Models.Cliente:
cfg.CreateMap<Cliente, CadastroModule.Model.Cliente>()
.ReverseMap()
// Map the `Endereco` property from `Cliente`
.ForMember(dest => dest.Endereco, opt => opt.MapFrom(src => src));
cfg.CreateMap<CadastroModule.Model.Cliente, Endereco>();
.ForMember(dest => dest.Bairro, opt => opt.MapFrom(src => src.EnderecoBairro))
.ForMember(dest => dest.CEP, opt => opt.MapFrom(src => src.EnderecoCEP))
.ForMember(dest => dest.Complemento, opt => opt.MapFrom(src => src.EnderecoComplemento))
.ForMember(dest => dest.Logradouro, opt => opt.MapFrom(src => src.EnderecoLogradouro))
.ForMember(dest => dest.Numero, opt => opt.MapFrom(src => src.EnderecoNumero));
Rather than map every property of Endereco, you could register Endereco as a source prefix instead:
cfg.CreateMap<Cliente, CadastroModule.Model.Cliente>()
.ReverseMap()
.ForMember(dest => dest.Endereco, opt => opt.MapFrom(src => src));
cfg.RecognizePrefixes("Endereco");
cfg.CreateMap<CadastroModule.Model.Cliente, Endereco>();
This solution presented, I think the author's post on two way mapping is worth a read. Essentially, AutoMapper was not built to map back onto domain entities. Of course you can use it however you wish, but that might explain why this kind of scenario is not supported out of the box.

AutoMapper: map DTO back to domain object with child objects [duplicate]

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

Categories