Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

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

Open luczito opened this issue 1 year ago • 2 comments

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

luczito avatar Mar 12 '24 12:03 luczito

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 avatar May 15 '24 09:05 coolqingcheng

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);

coolqingcheng avatar May 15 '24 09:05 coolqingcheng

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

luczito avatar May 30 '24 14:05 luczito