Flattening without prefixed names in child object in AutoMapper - c#

Consider these classes as source:
public class SourceParent
{
public int X { get; set; }
public SourceChild1 Child1 { get; set; }
public SourceChild2 Child2 { get; set; }
}
public class SourceChild1
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
}
public class SourceChild2
{
public int I { get; set; }
public int J { get; set; }
public int K { get; set; }
public int L { get; set; }
}
I'm trying to map the source to a destination similar to this:
public class Destination
{
public int X { get; set; }
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
public int I { get; set; }
public int J { get; set; }
public int K { get; set; }
public int L { get; set; }
}
Well, using this configuration, it is possible to do the mapping:
Mapper.CreateMap<SourceParent, Destination>()
.ForMember(d => d.A, opt => opt.MapFrom(s => s.Child1.A))
.ForMember(d => d.B, opt => opt.MapFrom(s => s.Child1.B))
.ForMember(d => d.C, opt => opt.MapFrom(s => s.Child1.C))
.ForMember(d => d.D, opt => opt.MapFrom(s => s.Child1.D))
.ForMember(d => d.I, opt => opt.MapFrom(s => s.Child2.I))
.ForMember(d => d.J, opt => opt.MapFrom(s => s.Child2.J))
.ForMember(d => d.K, opt => opt.MapFrom(s => s.Child2.K))
.ForMember(d => d.L, opt => opt.MapFrom(s => s.Child2.L));
Except that, when the child class has many properties, all of which having the same name with the parent, this is not a clean way.
Ideally, I'd like to tell AutoMapper to take Source.Child1 and Source.Child2 as a source too, and map every matching property names to the target (instead of specifying every single property); something like this:
Mapper.CreateMap<SourceParent, Destination>()
.AlsoUseSource(s => s.Child1)
.AlsoUseSource(s => s.Child2);

You can use .ConstructUsing to accomplish this. It's not the cleanest looking thing in the world, but it'll work:
/* Map each child to the destination */
Mapper.CreateMap<SourceChild1, Destination>();
Mapper.CreateMap<SourceChild2, Destination>();
Mapper.CreateMap<SourceParent, Destination>()
.ConstructUsing(src =>
{
/* Map A-D from Child1 */
var dest = Mapper.Map<Destination>(src.Child1);
/* Map I-L from Child2 */
Mapper.Map(src.Child2, dest);
return dest;
});
/* X will be mapped automatically. */
This should successfully map all of the properties.
Unfortunately, a call to .AssertConfigurationIsValid will fail, since properties I - L will not be mapped on the destination type for the mapping from SourceParent → Destination.
You could, of course, write a call to .Ignore for each one, but that would kind of defeat the purpose of getting rid of the nested mapping calls.
Your other option would be to leverage this awesome answer to ignore unmapped properties on each of the mappings.

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.

Conditional reverse mapping

I have the following mapper setup
public class InnerDest
{
public int B { get; set; }
}
public class OuterDest
{
public int A { get; set; }
public InnerDest Inner { get; set; }
}
public class FlatSource
{
public int A { get; set; }
public int? B { get; set; }
}
CreateMap<InnerDest, FlatSource>(MemberList.Source)
.ReverseMap();
CreateMap<OuterDest, FlatSource>()
.IncludeMember(dest => dest.Inner)
.ReverseMap()
.ForMember(dest => dest.Inner, opt => opt.Condition(source => source.B.HasValue()));
The idea is to make the property Inner optional, but it doesn't work. The property is always set regardless of the condition. Right now I do it with AfterMap, but I wonder if there is a better way of doing it.
You can drop ReverseMap. Or keep it if you have to:
c.CreateMap<InnerDest, FlatSource>(MemberList.Source).ReverseMap();
c.CreateMap<OuterDest, FlatSource>()
.IncludeMembers(s => s.Inner)
.ReverseMap()
.ForPath(dest => dest.Inner, opt => opt.Condition(context => context.Source.B.HasValue));
Check the execution plan.

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 merging objects issue

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);

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.

Categories