MapsterMapper / Mapster

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

Properties do not map properly #407

Open kzeratal opened 2 years ago

kzeratal commented 2 years ago

I have a class A that has a DateTime property, and class B that has a long property. Both properties have the same name. I've setup the rule for type DateTime converting to long in a config C1 which implements IRegister interface. And of course a config C2 for A converting to B But when I map from A to B, that DateTime property will always 0, unless I set the mapping rule in config C2.

andrerav commented 2 years ago

@kzeratal Can you share the mapping config and your custom type conversion code? Or alternatively set up a minimal test case if that's easier for you.

kzeratal commented 2 years ago

The following are the config and classes.

public class DateTimeConfig : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.ForType<DateTime, long>()
            .Map(dest => dest, src => src.ToLong());
        config.ForType<long, DateTime>()
            .Map(dest => dest, src => src.ToDateTime());
    }
}

Extensions look like

public long ToLong(DateTime time)
{
    return new DateTimeOffset(time).ToUnixTimeSeconds();
}

public DateTime ToDateTime(long value)
{
    return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(source);
}
public class C1
{
    public DateTime Time { get; set; }
}

public class C2
{
    public long Time { get; set; }
}

and the usage

var c1 = new C1 { Time = DateTime.UtcNow };
var c2Test1 = dbCotext.C1.ProjectToType<C2>().ToList().First(); // failed using EF 6
var c2Test2 = c1.Adapt<C2>(); // also failed at simple mapping, both two c2 objects' Time are 0
andrerav commented 2 years ago

Have you added breakpoints to the ToLong() and ToDateTime() methods and verified that they are being called as expected?

kzeratal commented 2 years ago

Well, they are not being called.

andrerav commented 2 years ago

I see. You have also checked that the DateTimeConfig.Register() method is called?

kzeratal commented 2 years ago

Yes, the register was executed. If there is no config, an System.InvalidCastException: 'Invalid cast from 'DateTime' to 'Int64'.' will occur.

DocSvartz commented 1 year ago

Hello @andrerav I think I know what this is about. Primitives do not support custom mapping. I described it in this #561. I even developed a specific solution) I'll test it on this case and report the result

DocSvartz commented 1 year ago

It needs some work, but it definitely works as I planned when the mapping To primitive.

 [TestMethod]
 public void MappingDatetimeToLong()
 {

     TypeAdapterConfig.GlobalSettings.ForType(typeof(Source407), typeof(Destination407))
        .MapToTargetPrimitive(true);

     TypeAdapterConfig<DateTime, long>
        .NewConfig()
        .MapToTargetWith((source, target) => true ? new DateTimeOffset(source).ToUnixTimeSeconds() : target);

     TypeAdapterConfig<long, DateTime>
        .NewConfig()
        .MapToTargetWith((source, target) => true ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(source) : target);

     var fromC1 = new DateTime(2023, 10, 27);
     var fromC2 = new DateTimeOffset(new DateTime(2025, 11, 23)).ToUnixTimeSeconds();

     var c1 = new Source407 { Time = fromC1 };
     var c2 = new Destination407 { Time = fromC2 };

     var _result = c1.Adapt<Destination407>(); // Work 
     var _resultLongtoDateTime = c2.Adapt<Source407>();

     _result.Time.ShouldBe(new DateTimeOffset(new DateTime(2023, 10, 27)).ToUnixTimeSeconds()); // Work                  
    // _resultLongtoDateTime.Time.ShouldBe(new DateTime(2025, 11, 23)); // ClassAdapter - not work; 
 }

    public class Source407
    {
        public DateTime Time { get; set; }
    }

    public class Destination407
    {
        public long Time { get; set; }
    }

update must work because Datetime not primitive. I just set the settings for the update script

DocSvartz commented 1 year ago

Work all

[TestMethod]
public void MappingDatetimeToLong()
{

    TypeAdapterConfig.GlobalSettings.ForType(typeof(Source407), typeof(Destination407))
       .MapToTargetPrimitive(true);

    TypeAdapterConfig<DateTime, long>
       .NewConfig()
       .MapToTargetWith((source, target) => true ? new DateTimeOffset(source).ToUnixTimeSeconds() : target);

    TypeAdapterConfig<long, DateTime>
       .NewConfig()
       .MapToTargetWith((source, target) => true ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(source).Date : target);

    var emptySource = new Source407() { Time = DateTime.UtcNow.Date };

    var fromC1 = new DateTime(2023, 10, 27);
    var fromC2 = new DateTimeOffset(new DateTime(2025, 11, 23)).ToUnixTimeSeconds();

    var c1 = new Source407 { Time = fromC1 };
    var c2 = new Destination407 { Time = fromC2 };

   var _result = c1.Adapt<Destination407>(); // Work 
    var _resultLongtoDateTime = c2.Adapt(emptySource); // work

    _result.Time.ShouldBe(new DateTimeOffset(new DateTime(2023, 10, 27)).ToUnixTimeSeconds()); // Work                  
    _resultLongtoDateTime.Time.ShouldBe(new DateTime(2025, 11, 22).Date); // work but it turns out to be a day less. Perhaps this is how it was intended
}
DocSvartz commented 1 year ago

@andrerav Yes, this is simply due to the lack of support for custom mapping processing from primitive type

DocSvartz commented 1 year ago

@andrerav in #650 i send this function.