MapsterMapper / Mapster

A fast, fun and stimulating object to object Mapper
MIT License
4.29k stars 327 forks source link

Mapster.Tool initialises property twice if property in derived class hides base class property #581

Open rafalka opened 1 year ago

rafalka commented 1 year ago

Mapster.Tool initialises property twice if property in derived class hides base class property.

Example: Declaration:

public class Src
{
    public  MyDateTime DateTime { get; set; }
}

public class DstBase
{
    public DateTime? DateTime { get; set; }
}

public class Dst:DstBase
{
    public new MyDateTime DateTime { get; set; }
}

Mapper configuration:

            config.ForType<Src, Dst>()
                .GenerateMapper(MapType.Map);

What gets generated:

        public static MapsterBug.Dst AdaptToDst(this MapsterBug.Src p1)
        {
            return p1 == null ? null : new MapsterBug.Dst()
            {
                DateTime = p1.DateTime == null ? null : new MapsterBug.MyDateTime(p1.DateTime.DateTime),
                DateTime = (System.DateTime?)p1.DateTime.DateTime.Value
            };
        }
    }

Sample project:

MapsterBugDuplicatePropertiesInitialisation.zip

rafalka commented 1 year ago

Hi, I managed to find a workaround for this issue. We can create a rule for ignoring member using .IgnoreMember().
As this is type specific, we have to use a .NewConfig<>() for desired mapping configuration.

Example:

config.NewConfig<Src, Dst>()
    .IgnoreHiddenAndReadOnlyProperties()
    .GenerateMapper(MapType.Map);

IgnoreHiddenAndReadOnlyProperties() extension method definition:

    public static TypeAdapterSetter<TSource, TDestination> IgnoreHiddenAndReadOnlyProperties<TSource, TDestination>(
        this TypeAdapterSetter<TSource, TDestination> config) =>
        config.IgnoreMember((m, s) =>
        {
            if (m.Info is not PropertyInfo pi) return true;

            var isDestination = s == MemberSide.Destination;
            return IsPropertyHidden(pi, isDestination ? typeof(TDestination) : typeof(TSource))
                   || (isDestination && !IsPropertyWritable(pi));
        });

    public static bool IsPropertyWritable(this PropertyInfo pi) =>
        pi is { CanWrite: true } && pi.GetSetMethod() is { IsPublic: true };

    public static bool IsPropertyHidden(this PropertyInfo pi, Type dstType)
    {
        var e = dstType.GetProperty(pi.Name,
            BindingFlags.Public |  BindingFlags.Instance | BindingFlags.DeclaredOnly);

        return e is not null && pi.DeclaringType != e.DeclaringType;
    }

Is not perfect as it does not work with .TwoWays() - you'll have to manually add configuration for reverse mapping.

andrerav commented 1 year ago

Thanks @rafalka, I'm leaving this open in case anyone else has similar problems or if anyone has suggestions on how to improve this. Feel free to open a PR to add this extension method to the main code base.