MapsterMapper / Mapster

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

Mapster re-writing value when mapping resulting in wrong destination type #684

Closed luczito closed 1 month ago

luczito commented 3 months ago

I have the following classes and patterns that Mapster can't work with. Specifically the pattern for overwriting the DerivedPayment in each of the derived classes. When debugging it shows that the "setter" gets the correct value, Dom/DtoDerivedPayment. But when the "getter" is called this value is somehow overwritten to the base instance of Dom/DtoPayment instead, resulting in a "System.InvalidCastException : Unable to cast object of type 'DomPayment' to type 'DomDerivedPayment'."

using System.Reflection;
using Mapster;
using NUnit.Framework;

public abstract class DomBase
{
    public DomPayment Payment { get; set; }
}

public class DomDerived : DomBase
{
    public new DomDerivedPayment Payment
    {
        get => (DomDerivedPayment)base.Payment;
        set => base.Payment = (DomPayment)value;
    }
}

public class DomPayment
{
    public bool Prop { get; set; }
}

public class DomDerivedPayment : DomPayment
{
    public bool Test { get; set; }
}

public abstract class DtoBase
{
    public DtoPayment Payment { get; set; }
}

public class DtoDerived : DtoBase
{
    public new DtoDerivedPayment Payment
    {
        get => (DtoDerivedPayment)base.Payment;
        set => base.Payment = (DtoPayment)value;
    }
}

public class DtoPayment
{
    public bool Prop { get; set; }
}

public class DtoDerivedPayment : DtoPayment
{
    public bool Test { get; set; }
}

public class Mapping : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.NewConfig<DomBase, DtoBase>()
            .Include<DomDerived, DtoDerived>();

        config.NewConfig<DtoBase, DomBase>()
            .Include<DtoDerived, DomDerived>();

        config.NewConfig<DtoPayment, DomPayment>()
            .Include<DtoDerivedPayment, DomDerivedPayment>();

        config.NewConfig<DomPayment, DtoPayment>()
            .Include<DomDerivedPayment, DtoDerivedPayment>();
    }
}

[TestFixture]
public class MapsterTests
{
    [Test]
    public void DtoToDomain()
    {
        var config = TypeAdapterConfig.GlobalSettings;
        config.Scan(Assembly.Load("test.repo"));
        var dto = new DtoDerived();
        dto.Payment = new DtoDerivedPayment();
        dto.Payment.Prop = true;
        dto.Payment.Test = true;
        var domain = dto.Adapt<DomDerived>();
        Assert.That(domain.Payment.Prop, Is.EqualTo(dto.Payment.Prop));
    }

    [Test]
    public void DomainToDto()
    {
        var domain = new DomDerived();
        domain.Payment = new DomDerivedPayment();
        var dto = domain.Adapt<DtoDerived>();
        Assert.That(dto.Payment.Prop, Is.EqualTo(domain.Payment.Prop));
    }
}
coolqingcheng commented 1 month ago

I also encountered it. The Id of my parent class is int, and the subclass uses new to modify the guid, and a type conversion error is reported.

  ---> Mapster.CompileException: Error while compiling
source=System.Nullable`1[System.Guid]
destination=System.Int32
type=Map
coolqingcheng commented 1 month ago

Even if I configure ignore, I still get an error

 config.ForType<EditUserModel, User>()
            .IgnoreIf((model, user) => model.Id == null, a => a.Id)
            .IgnoreNullValues(true);
luczito commented 1 month ago

I fixed this by overwriting the value with an aftermapping statement in my mapping configs. Don't know if that can help you @coolqingcheng

config.NewConfig<DtoDerived, DomDerived>()
  .TwoWays()
  .AfterMapping((dto, dom) => dom.Payment = dto.Payment.Adapt<DomDerivedPayment>());