Automapper - Ignore mapping with condition - c#

I'm using automapper and I would like to know if it's possible to ignore a mapping of a field when that's null.
That's my code:
.ForMember(dest => dest.BusinessGroup_Id,
opt => opt.MapFrom(src => (int)src.BusinessGroup))
src.BusinessGroup type = "enum"
dest.BusinessGroup_Id = int
The objective it's to ingore that Mapping if src.BusinessGroup = null.

I think NullSubstitute option will do the trick
.ForMember(d => d.BusinessGroup_Id, o => o.MapFrom(s => (int?)s.BusinessGroup));
.ForMember(d => d.BusinessGroup_Id, o => o.NullSubstitute(0));
BTW you can write your conditions in mapping action:
.ForMember(d => d.BusinessGroup_Id,
o => o.MapFrom(s => s.BusinessGroup == null ? 0 : (int)s.BusinessGroup));
UPDATE if you cannot assign some default value to your property, you can just ignore it and map only not nulls:
.ForMember(d => d.BusinessGroup_Id, o => o.Ignore())
.AfterMap((s, d) =>
{
if (s.BusinessGroup != null)
d.BusinessGroup_Id = (int)s.BusinessGroup;
});

Related

Automapper- Map and add elements to list conditionally

I have a unique requirement when mapping some elements using Automapper.
I am not finding any effective solution with built scenarios:
I want to add phone number details to the contacts list if the phone number is not null
I want to add email address details to the contacts list if the email is not null
CreateMap<UserModel, UserDefinition>()
.ForMember(d => d.Id, o => o.Ignore())
.ForMember(d => d.UserName, o => o.MapFrom(s => s.Username))
.ForMember(d => d.Contacts, o =>
new List<UserContactDefinition>()
{
o.MapFrom(s => !string.IsNullOrWhiteSpace(s.PhoneNumber) ?
new UserContactDefinition
{
Type = ContactType.Phone,
IsPrimary = true,
Label = s.PhoneType,
Value = s.PhoneNumber
}: null,
o.MapFrom(s => !string.IsNullOrWhiteSpace(s.ContactEmail) ?
new UserContactDefinition
{
Type = ContactType.Email,
IsPrimary = true,
Label = s.EmailType,
Value = s.Email
}: null
}
);
This code is not working and I don't want to add empty elements if there is no value.
Any leads to this?
For your scenario, you need the Custom Value Resolver to map the destination member for the Contacts property.
Implement UserContactDefinitionListResolver custom value resolver.
public class UserContactDefinitionListResolver : IValueResolver<UserModel, UserDefinition, List<UserContactDefinition>>
{
public List<UserContactDefinition> Resolve(UserModel src, UserDefinition dst, List<UserContactDefinition> dstMember, ResolutionContext ctx)
{
dstMember = new List<UserContactDefinition>();
if (!string.IsNullOrWhiteSpace(src.PhoneNumber))
dstMember.Add(new UserContactDefinition
{
Type = ContactType.Phone,
IsPrimary = true,
Label = src.PhoneType,
Value = src.PhoneNumber
});
if (!string.IsNullOrWhiteSpace(src.ContactEmail))
dstMember.Add(new UserContactDefinition
{
Type = ContactType.Email,
IsPrimary = true,
Label = src.EmailType,
Value = src.ContactEmail
});
return dstMember;
}
}
Add mapping configuration/profile for the member Contacts to use the UserContactDefinitionListResolver.
CreateMap<UserModel, UserDefinition>()
.ForMember(d => d.Id, o => o.Ignore())
.ForMember(d => d.UserName, o => o.MapFrom(s => s.Username))
.ForMember(d => d.Contacts, o => o.MapFrom(new UserContactDefinitionListResolver()));
Demo # .NET Fiddle

Automapper - conditional mapping on destination

Mapping:
.ForMember(dest => dest.DisplayName, opt =>
{
opt.PreCondition(location => location.Parent != null);
opt.MapFrom(src => src.Name + ", " + src.Parent.Name);
})
.ForMember(dest => dest.DisplayName, opt =>
{
opt.PreCondition((src, dest, context) => dest.DisplayName == null);
opt.MapFrom(src => src.Name);
})
Expected result:
If the first condition is met don't override the mapping.
What actually happens:
The second mapping is overriding the first mapping.
How can I solve this?
It doesn't work because you are overwriting previous mapping expressions calling another ForMember() for the same member, which is your case is DisplayName. Consider such case:
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "1"))
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "2"))
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "3"));
Which value will be mapped to DisplayName?
3
So in your case, your first conditional mapping expression is overwriten by the second one. To make it work, join the conditional mapping into one mapping expression:
.ForMember(
dest => dest.DisplayName,
opts => opts.MapFrom((src, dest) =>
{
if (src.Parent != null)
{
return string.Join(", ", src.Name, src.Parent.Name);
}
else
{
if (dest.DisplayName is null)
{
return src.Name;
}
else
{
return "Some other value when no conditions were met.";
}
}
}));
It would be a cool feature to have but I don't see it anywhere in Automapper documentation.
This should however work in your case if the logic is not more complex.
.ForMember(dest => dest.DisplayName, opt =>
{
opt.MapFrom(src => src.Name + (location.Parent != null ? ", " + src.Parent.Name : null));
})

C# AutoMapper: set destination value by source value after validation in conditional mapping

I'm fairly new to AutoMapper and want to know how to set a destination member to a value based on a DIFFERENT source property value and if that value is null I just want to apply the default behaviour of Automapper (keep destination value when the source is null)
CreateMap<ClassA, ClassA>()
.ForMember(dest => dest.PropertyA, opt =>
opt.MapFrom(src => src.PropertyB!= null ? null : opt.UseDestinationValue())
)
This doesn't work (don't compile) the opt.UseDestinationValue() , what option can I use here?
Please help
Try setting a precondition for mapping destination property.
CreateMap<ClassA, ClassA>().ForMember(dest => dest.PropertyA, opt => opt.PreCondition((src, dest) => src.PropertyB != null));
This will map PropertyA only when PropertyB is not null. I did try a quick sample which gave the desired result.
I think you can use the PreCondition option For Mapping Property
CreateMap<ClassA, ClassA>()
.ForMember(dest => dest.PropertyA, opt => {
opt.PreCondition(src => src.PropertyB!= null);
opt.MapFrom(src => src.PropertyB);
});
Hope to help you
You can do as follows:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA,ClassA>()
.ForMember(dest => dest.PropertyA, opt => opt.Condition(src => (src.PropertyB!= null)));
});
Or as follows:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA,ClassA>()
.ForMember(dest => dest.PropertyA, opt => {
opt.PreCondition(src => (src.PropertyB!=null));
opt.MapFrom(src => src.PropertyB); // mapping process takes place here
});
});
But the difference is that, the later runs sooner in the mapping process.
There is an excellent documentation in setting conditions for automapper:
https://docs.automapper.org/en/stable/Conditional-mapping.html

Keep property of source object to destination property when mapping objects using AutoMapper Map function

I want to map most properties of the current object (this a FinancialBase instance) to another object (the 'destination' object, the schedule, an instance of a Schedule class). However, I need to keep a small set of the destination's properties.
I've got it working with a 'hack' where I capture the values explicitly then use these in the AfterMap function. See example code.
var id = schedule.Id;
var parentId = schedule.ParentId;
var scheduleNo = schedule.ScheduleNo;
var schName = schedule.SchName;
var config = new MapperConfiguration(
cfg => cfg.CreateMap<FinancialBase, Schedule>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.ParentId, opt => opt.Ignore())
.ForMember(d => d.ScheduleNo, opt => opt.Ignore())
.ForMember(d => d.SchName, opt => opt.Ignore())
.AfterMap((s, d) => d.Id = id)
.AfterMap((s, d) => d.ParentId = parentId)
.AfterMap((s, d) => d.ScheduleNo = scheduleNo)
.AfterMap((s, d) => d.SchName = schName));
var mapper = config.CreateMapper();
schedule = mapper.Map<Schedule>(this);
I would prefer not to use the first four lines of my example but instead have them included using a conventional AutoMapper lambda expression. Possible?
I'd just use mapping to an existing object:
var existingSchedule = new Schedule()
{
Id = 12,
ParentId = 34,
ScheduleNo = 56,
SchName = "Foo",
};
var schedule = mapper.Map(this, existingSchedule);
And in the configuration leave the Ignore() lines but remove those with AfterMap() as they are no longer needed:
var config = new MapperConfiguration(
cfg => cfg.CreateMap<FinancialBase, Schedule>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.ParentId, opt => opt.Ignore())
.ForMember(d => d.ScheduleNo, opt => opt.Ignore())
.ForMember(d => d.SchName, opt => opt.Ignore()));

Automapper.mapper.map doesn't work but mapper.dynamicmap work

First of all, sorry for my bad english! I'm french.
I tried to find on google but without any result so, this is my automapper configuration :
public void CreateMap(IMapperConfiguration cfg)
{
cfg.CreateMap<GroupeApplication, GroupeApplicationContract>();
cfg.CreateMap<Application, ApplicationContract>()
.ForMember(d => d.GroupeApplicationId, o => o.MapFrom(s => s.GroupeApplication != null ? s.GroupeApplication.Id : Guid.Empty));
cfg.CreateMap<Application, ApplicationWithAscendantContract>()
.ForMember(d => d.GroupeApplication, o => o.MapFrom(s => Mapper.Map<GroupeApplicationContract>(s.GroupeApplication)));
cfg.CreateMap<GroupeApplication, GroupeApplicationWithDescendantContract>()
.ForMember(d => d.Applications, o => o.MapFrom(s => Mapper.Map<List<ApplicationContract>>(s.Applications)));
Mapper.AssertConfigurationIsValid();
}
In fact, groupeapplication has list of application, and Mapper.Map failed when it try to map my applications collection... this is the error message :
Missing type map configuration or unsupported mapping.
Mapping types:
Application -> ApplicationContract
UGO.Distribution.Domain.Application -> UGO.Distribution.Shared.Contracts.ApplicationContract
Destination path:
List`1[0]
Source value:
UGO.Distribution.Domain.Application
If I replace the last config with this it works (I replace Mapper.Map by Mapper.DynamicMap) :
cfg.CreateMap<GroupeApplication, GroupeApplicationWithDescendantContract>()
.ForMember(d => d.Applications, o => o.MapFrom(s => Mapper.DynamicMap<List<ApplicationContract>>(s.Applications)));
I could be satisfied, but that is not for me is that this method is deprecated in automapper 4.0.3 !!
Is there any explanation or a better solution for this problem?
Instead of putting ForMember(...), try:
ForMember<ObjectThatYouWantMap>(...);

Categories