Search code examples
c#automapper

Fill properties based on condition of specific properties


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?


Solution

  • 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>();
        }
    }