Conditional reverse mapping - c#

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.

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 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 specify a rule for mapping one property type to another

I have a set of business objects that I need to persist as entities. The business objects have many properties of type TimeSpan. I would like to map them to long properties on my entities.
public class BusinessObject
{
public string Name { get; set; }
public TimeSpan ProcessingTime { get; set; }
public TimeSpan WaitTime { get; set; }
public TimeSpan RecordTime { get; set; }
}
public class Entity
{
public string Name { get; set; }
public long ProcessingTime { get; set; }
public long WaitTime { get; set; }
public long RecordTime { get; set; }
}
public void CreateMapping()
{
Mapper.CreateMap<BusinessObject, Entity>()
.ForMember(e => e.ProcessingTime, opt => opt.MapFrom(src => src.ProcessingTime.TotalMilliseconds))
.ForMember(e => e.WaitTime, opt => opt.MapFrom(src => src.WaitTime.TotalMilliseconds))
.ForMember(e => e.RecordTime, opt => opt.MapFrom(src => src.RecordTime.TotalMilliseconds));
}
Is there a way that I can specify in the mapping that all Timespan properties should be mapped to long for that mapping, without having to write out each one?
It looks like you can override the TypeConverter that is instantiated for the type TimeSpan in
https://github.com/AutoMapper/AutoMapper/blob/develop/src/AutoMapper/Mappers/TypeConverterMapper.cs

Flattening without prefixed names in child object in AutoMapper

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.

Automapper - set value based on type of object mapped

Here's my DTO:
public class DiaryEventType_dto
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Group { get; set; }
public bool Redundant { get; set; }
public string Type { get; set; }
}
And it maps to two possible entity types:
public partial class UserDiaryEventType
{
public System.Guid Id { get; set; }
public string Name { get; set; }
public string TypeGroup { get; set; }
public bool Redundant { get; set; }
}
public partial class SystemDiaryEventType
{
public System.Guid Id { get; set; }
public string Name { get; set; }
public string TypeGroup { get; set; }
public bool Redundant { get; set; }
}
The "Type" property is meant to distinguish which type the DTO was originally mapped from (why would I want to do this rather than have two separate DTO classes? Legacy code, that's why - too much pain to change it all).
Ideally I'd like to populate this during automapping, otherwise the mapper will throw a wobbler at me because "Type" isn't mapped:
Mapper.CreateMap<Entities.UserDiaryEventType, DiaryEventType_dto>()
.ForMember(m => m.Group, o => o.MapFrom(s => s.TypeGroup));
Mapper.CreateMap<DiaryEventType_dto, Entities.UserDiaryEventType>()
.ForMember(m => m.TypeGroup, o => o.MapFrom(s => s.Group));
Mapper.CreateMap<Entities.SystemDiaryEventType, DiaryEventType_dto>()
.ForMember(m => m.Group, o => o.MapFrom(s => s.TypeGroup));
Mapper.CreateMap<DiaryEventType_dto, Entities.SystemDiaryEventType>()
.ForMember(m => m.TypeGroup, o => o.MapFrom(s => s.Group));
But I can't figure out the syntax for doing so. Something like:
//pseudo code
Mapper.CreateMap<DiaryEventType_dto, Entities.UserDiaryEventType>()
.SetValue("User");
Mapper.CreateMap<DiaryEventType_dto, Entities.SystemDiaryEventType>()
.SetValue("System");
Is it possible?
ResolveUsing lets you use a custom value or calculation.
Mapper.CreateMap<Entities.UserDiaryEventType, DiaryEventType_dto>()
.ForMember(m => m.Group, o => o.MapFrom(s => s.TypeGroup))
.ForMember(m => m.Group, o => o.ResolveUsing(s => "User"));

Categories