Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

Mapping from one existing object to another and Mapster is returning generated and unwanted values

Open ryan-singleton opened this issue 3 years ago • 1 comments

I am performing a tech stack spike for our service infrastructure. We're taking a look at our mapping right now, which is currently AutoMapper. Pretty standard situation.

The benchmark comparisons and high level syntax comparisons are promising. However, the documentation for this library is presenting strong arguments against making the switch. I have to recommend that you populate the your public members with documentation so the IDE can tell devs what it is that they're looking at. And it would compliment the existing documentation on this site to probably reduce your support requests significantly.

For example, I suspect that the answer to my question will lie in TypeAdapterConfigSettings class, but when I look at its members to see what options I have, there is no elaboration. Placing your cursor over each public member and hitting "/" three times as a matter of due diligence goes a long way toward adoptability.

Here's what I'm dealing with.

public class UserAccount : ShardableDocument
{   
    public string? Email { get; set; }    
    public string? GivenName { get; set; }
    public string? FamilyName { get; set; }
    public DateTime Created { get; set; }
    public DateTime Modified { get; set; }
}

The ShardableDocument base class has an Id field. I want to be able to update users with my CRUD operations, so we have this request type

public class UpdateUser
{
    public string? Id { get; set; }
    public string? Email { get; set; }
    public string? GivenName { get; set; }
    public string? FamilyName { get; set; }
}

This is what I'm passing to my ServiceMapper

var config = new TypeAdapterConfig();
        config.ForType<UpdateUser, UserAccount>()
            .Map(dest => dest.Modified, src => DateTime.UtcNow)
            .IgnoreNullValues(true);

And in my User processor, there's a line that looks like this. _mapper.Map(updateUser, userAccount);

It is mapping the updateUser request from the client to the userAccount that was found by a database get by ID call.

Intuitively, I would expect that any properties of the same name would copy from the source object to the destination object unless the source object does not have that member, or the member is null. IN WHICH CASE, the destination would be left alone. Created would not be overwritten, it already had a date. Id would not be overwritten, it already had an Id. Or, at least in this case, it would receive the one from updateUser which has a matching Id.

It is not being left alone. Created is being set to DateTime.UtcNow and Id is being set to a random GUID. Is this behavior intended?

Edit: For additional info, if I change the mapping to this

var config = new TypeAdapterConfig();
        config.ForType<UpdateUser, UserAccount>()
            .Map(dest => dest.Email, source => source.Email)
            .Map(dest => dest.FamilyName, source => source.FamilyName)
            .Map(dest => dest.GivenName, source => source.GivenName)
            .Map(dest => dest.Modified, src => DateTime.UtcNow)
            .Ignore(dest => dest.Created)
            .Ignore(dest => dest.Id!)
            .IgnoreNullValues(true);

I get the expected result. So I know that my TypeAdapterConfig is being referenced correctly. I suppose that I just do not see why I have to manually map and ignore this way. Seems to be favoring the sort of logic you would have in generating an object when we are, in fact, not intending to generate a new object.

ryan-singleton avatar Apr 19 '22 16:04 ryan-singleton

This was most likely due to a false positive identification of your class as a record.

These classes look exactly as you described in the example UserAccount, ShardableDocument?

They have only one public constructor without parameters, public UserAccount(), public ShardableDocument()?

After the fix It works like this. Did you expect this behavior?


 /// <summary>
 /// https://github.com/MapsterMapper/Mapster/issues/427
 /// </summary>
 [TestMethod]
 public void UpdateNullable()
 {
     var _source = new UserAccount("123", "[email protected]", new DateTime(2023, 9, 24));
     var _update = new UpdateUser
     {
         Id = "123",
     };
     var configDate = new TypeAdapterConfig();

     configDate.ForType<UpdateUser, UserAccount>()
         .Map(dest => dest.Modified, src => new DateTime(2025, 9, 24))
         .IgnoreNullValues(true);

     _update.Adapt(_source, configDate);

     var _sourceEmailUpdate = new UserAccount("123", "[email protected]", new DateTime(2023, 9, 24))
     { Modified = new DateTime(2023,9,25)};
     var _updateEmail = new UpdateUser
     {
         Email = "[email protected]",
     };

     var config = new TypeAdapterConfig();
     config.ForType<UpdateUser, UserAccount>()
         .IgnoreNullValues(true);

     var _resultEmail = _updateEmail.Adapt(_sourceEmailUpdate, config);

     _source.Id.ShouldBe("123");
     _source.Created.ShouldBe(new DateTime(2023, 9, 24));
     _source.Modified.ShouldBe(new DateTime(2025, 9, 24));
     _source.Email.ShouldBe("[email protected]");
     _sourceEmailUpdate.Id.ShouldBe("123");
     _sourceEmailUpdate.Created.ShouldBe(new DateTime(2023, 9, 24));
     _sourceEmailUpdate.Modified.ShouldBe(new DateTime(2023, 9, 25));
     _sourceEmailUpdate.Email.ShouldBe("[email protected]");

 }

DocSvartz avatar Oct 21 '23 10:10 DocSvartz