I have the following scenario with the classes:
public class ApiRequest
{
public int? FwVersion { get; set; }
public int? OsVersion { get; set; }
public int PageSize { get; set; }
public string Owner { get; set; } = String.Empty;
}
and
public class DbRequest
{
public int PageSize { get; set; }
public string Owner { get; set; } = String.Empty;
public int FwVersion { get; set; }
public bool FwVersionSpecified { get; set; }
public int OsVersion { get; set; }
public bool OsVersionSpecified { get; set; }
}
Now I convert objects from ApiRequest
to DbRequest
. The way I have to do that is the following:
For each nullable property in my ApiRequest
, there is a non-nullable property in the DbRequest
class and another boolean property with the same name and the "SpecifiedSuffix". I fill the non-nullable property using GetValueOrDefault()
and the appropriate specified property using HasValue
. Here is an example of my AutoMapper configuration:
public class MappingConfiguration : Profile
{
public MappingConfiguration()
{
// Fix mappings
CreateMap<ApiRequest, DbRequest>()
.ForMember(dst => dst.FwVersion, opt => opt.MapFrom(
src => src.FwVersion.GetValueOrDefault()
))
.ForMember(dst => dst.FwVersionSpecified, opt => opt.MapFrom(
src => src.FwVersion.HasValue
))
.ForMember(dst => dst.OsVersion, opt => opt.MapFrom(src => src.OsVersion.GetValueOrDefault()))
.ForMember(dst => dst.OsVersionSpecified, opt => opt.MapFrom(src => src.OsVersion.HasValue));
}
}
This works. However, my issue is I have other classes like ApiRequest
/DbRequest
that have a lot of nullable and specified properties. Is there an easier way to map them without going to each field one by one manually?
Three approaches to achieve based on your scenario.
Approach 1
For the destination member with "Specified" as a postfix, you must implement the mapping rule with ForMember()
. For the remaining members, you can use ForAllMembers()
to map the members only when the source member is not null.
CreateMap<ApiRequest, DbRequest>()
.ForMember(dst => dst.FwVersionSpecified, opt => opt.MapFrom(
src => src.FwVersion.HasValue
))
.ForMember(dst => dst.OsVersionSpecified, opt => opt.MapFrom(src => src.OsVersion.HasValue))
.ForAllMembers(o => o.Condition((src, dest, value) => value != null));
Approach 2
If Approach 1 is not what you are looking for, you may look for System.Reflection and handle the scenario for the destination member with/without the "Specified" postfix.
using System.Reflection;
CreateMap<ApiRequest, DbRequest>()
.ForAllMembers(o => o.MapFrom((src, dest, value) =>
{
string specified = "Specified";
bool isSpecified = o.DestinationMember.Name.EndsWith(specified);
if (isSpecified)
{
PropertyInfo prop = typeof(ApiRequest).GetProperty(o.DestinationMember.Name[0..^specified.Length]);
return prop.GetValue(src) != null;
}
else
{
PropertyInfo prop = typeof(ApiRequest).GetProperty(o.DestinationMember.Name);
return prop.GetValue(src) ?? default;
}
}));
Approach 3
The shortest and simplest approach that is provided by @LucianBargaoanu (the AutoMapper master) is to work with Recognizing pre/postfixes.
public class MappingConfiguration : Profile
{
public MappingConfiguration()
{
RecognizeDestinationPostfixes("Specified");
CreateMap<ApiRequest, DbRequest>();
}
}