AgileMapper icon indicating copy to clipboard operation
AgileMapper copied to clipboard

How to remove null checks in source mappings?

Open C-Wal opened this issue 2 years ago • 4 comments

If I map properties then the compiled plan says every source property value referenced in the Map() must not be null. How do I remove this null check?

In this instance I'm mapping an addressline1 from multiple source properties, but the plan is forcing all source properties to be non-null, which I clearly don't want as in the mapping I'm ignoring the null lines and concating the non-null.

C-Wal avatar Apr 08 '22 14:04 C-Wal

Hello!

Sounds like you've got a custom source->target member configuration where you're handling null-checking yourself and the built-in null-checking is getting in the way? Could you give me an example of what you're trying to map?

Cheers, Steve

SteveWilkes avatar Apr 09 '22 09:04 SteveWilkes

The mapping is between two classes named Address with mostly different properties. The target.AddressLine1 is populated with a lot of string.IsNullOrEmpty checks on the source properties.

I don't have the code right now but here is the map that is produced:

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// Map Address -> Address
// Rule Set: CreateNew
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

aToAData =>
{
    Address sourceAddress;
    try
    {
        sourceAddress = aToAData.Source;

        var address = new Address();
        // No data sources for ID
        // No data sources for Country_ID
        // No data sources for Country or any of its child members

        if (((((sourceAddress.Thoroughfare != null) && (sourceAddress.DependentThoroughfare != null)) &&
            (sourceAddress.BuildingNumber != null)) &&
            (sourceAddress.Building != null)) &&
            (sourceAddress.SubBuilding != null))
        {
            address.AddressLine1 = new[]
            {
                string.IsNullOrWhiteSpace(sourceAddress.SubBuilding)
                    ? string.Empty
                    : sourceAddress.SubBuilding.Trim() + ", ",
                string.IsNullOrWhiteSpace(sourceAddress.Building)
                    ? string.Empty
                    : sourceAddress.Building.Trim() + ", ",
                string.IsNullOrWhiteSpace(sourceAddress.BuildingNumber)
                    ? string.Empty
                    : sourceAddress.BuildingNumber.Trim() + ", ",
                string.IsNullOrWhiteSpace(sourceAddress.DependentThoroughfare)
                    ? string.Empty
                    : sourceAddress.DependentThoroughfare.Trim() + ", ",
                string.IsNullOrWhiteSpace(sourceAddress.Thoroughfare)
                    ? string.Empty
                    : sourceAddress.Thoroughfare.Trim()
            }.Trim();
        }

        if ((sourceAddress.DependentLocality != null) && (sourceAddress.DoubleDependentLocality != null))
        {
            address.AddressLine2 = (string.IsNullOrWhiteSpace(sourceAddress.DoubleDependentLocality)
                ? string.Empty
                : sourceAddress.DoubleDependentLocality.Trim() + ", ") + (string.IsNullOrWhiteSpace(sourceAddress.DependentLocality)
                ? string.Empty
                : sourceAddress.DependentLocality.Trim());
        }

        if (sourceAddress.Town != null)
        {
            address.PostalTown = string.IsNullOrWhiteSpace(sourceAddress.Town) ? string.Empty : sourceAddress.Town.Trim();
        }

        if (sourceAddress.County != null)
        {
            address.County = string.IsNullOrWhiteSpace(sourceAddress.County) ? string.Empty : sourceAddress.County.Trim();
        }
        else
        {
            address.County = sourceAddress.County;
        }

        // No way to populate FullAddress (readonly string)

        if (sourceAddress.PostCode != null)
        {
            address.PostCode = string.IsNullOrWhiteSpace(sourceAddress.PostCode)
                ? string.Empty
                : sourceAddress.PostCode.Trim();
        }
        else
        {
            address.PostCode = sourceAddress.PostCode;
        }

        return address;
    }
    catch (Exception ex)
    {
        throw MappingException.For(
            "CreateNew",
            "Address",
            "Address",
            ex);
    }
}

As you can see, target.AddressLine1 is only composed if all the source properties are non-null.

Also I notice that the if gains an else only if the properties have matching names. For example, County > County has an if-else but Town > PostalTown only has an if. Is that intentional?

C-Wal avatar Apr 11 '22 08:04 C-Wal

We have worked around it for now by using Before.CreatingTargetInstances.Call() to ensure each source property has a value but suspect there's a nicer way to deal with it.

C-Wal avatar Apr 11 '22 08:04 C-Wal

Hello!

The null checks are the mapper protecting against null strings in the Trim() calls - it isn't smart enough to figure out you've already handled it, unfortunately.

I suppose the proper solution would be a configuration option to indicate you're going to handle null-checking yourself, but (if I've understood the mapping correctly) - maybe this would be neater:

Mapper.WhenMapping
    .From<Source.Address>()
    .To<Target.Address>()
    .Map((sourceAddress, _) => string.Join(", ", new[]
        {
            sourceAddress.SubBuilding,
            sourceAddress.Building,
            sourceAddress.BuildingNumber,
            sourceAddress.DependentThoroughfare,
            sourceAddress.Thoroughfare
        }.Where(addressPart => !string.IsNullOrWhiteSpace(addressPart))
         .Select(addressPart => addressPart.Trim())))
    .To(targetAddress => targetAddress.AddressLine1);

Again unfortunately, testing this turned up a bug where the mapper falls over trying to figure out a null-check for addressPart.Trim() - there's a fix that in the latest master branch.

Cheers,

Steve

SteveWilkes avatar Apr 12 '22 20:04 SteveWilkes