MapsterMapper / Mapster

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

Mapping 2 objects to poco - issue when mapping the other way #604

Open ChristoGC opened 1 year ago

ChristoGC commented 1 year ago

I thought I would flag an issue I observed.

When following the example in https://github.com/MapsterMapper/Mapster/wiki/Custom-mapping Example 2: Mapping 2 objects to poco there seems to be an issue when reversing the mapping. Creating a new tuple from from a poco is fine, but updates do not work.

Example code

const bool ShowScript = true;

void Main()
{
    TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo();
    string script;

    // Tuple to POCO
    TypeAdapterConfig<(AuthorizationGroup authorizationGroup, GroupDetails groupDetails), GroupDM>
        .NewConfig()
            .Map(dest => dest, src => src.authorizationGroup)
            .Map(dest => dest, src => src.groupDetails);

    AuthorizationGroup authorizationGroup = new() { Description = "TEST" };
    GroupDetails groupDetails = new() { GroupId = 10 };

    if(ShowScript)
    {
        script = (authorizationGroup, groupDetails).BuildAdapter()
                    .CreateMapExpression<GroupDM>()
                    .ToScript();

        Console.WriteLine(script);

        script = (authorizationGroup, groupDetails).BuildAdapter()
                    .CreateMapToTargetExpression<GroupDM>()
                    .ToScript();

        Console.WriteLine(script);
    }

    // Lets create a new instance of GroupDM: Works
    var obj = (authorizationGroup, groupDetails).Adapt<GroupDM>();

    // Now lets update
    authorizationGroup.Description = "UPDATED TEST";
    groupDetails.GroupId = 50;

    // Now lets update GroupDM: Works
    (authorizationGroup, groupDetails).Adapt(obj);

    // POCO to Tuple
    TypeAdapterConfig<GroupDM, (AuthorizationGroup authorizationGroup, GroupDetails groupDetails)>
    .NewConfig()
        .Map(dest => dest.authorizationGroup, src => src)
        .Map(dest => dest.groupDetails, src => src);

    GroupDM groupDM = new() { Name = "Hello world", Description = "Jon Bon Jovi" };

    if (ShowScript)
    {
        script = groupDM.BuildAdapter()
                    .CreateMapExpression<(AuthorizationGroup authorizationGroup, GroupDetails groupDetails)>()
                    .ToScript();

        Console.WriteLine(script);

        script = groupDM.BuildAdapter()
                    .CreateMapToTargetExpression<(AuthorizationGroup authorizationGroup, GroupDetails groupDetails)>()
                    .ToScript();

        Console.WriteLine(script);
    }

    // Create a new instance of (AuthorizationGroup, GroupDetails): This works
    var tuple = groupDM.Adapt<(AuthorizationGroup authorizationGroup, GroupDetails groupDetails)>();

    // Now lets update poco
    groupDM.GroupId = 99;
    groupDM.Description = "Led Zep";

    //Now lets update tuple: Doesn't work
    groupDM.Adapt(tuple);
}

public class GroupDM 
{
    public int GroupId;
    public string Name { get; set; }
    public string Description { get; set; }
}

public class AuthorizationGroup
{
    public string Name { get; set; }
    public string Description { get; set; }
}

public class GroupDetails
{
    public int GroupId { get; set; }
}

The expression scripts produced suggest that CreateMapToTargetExpression on poco to tuple is incorrect as it is exactly the same as CreateMapExpression

// New: Tuple to POCO
public GroupDM Main(ValueTuple<AuthorizationGroup, GroupDetails> p1)
{
    return new GroupDM()
    {
        Name = p1.Item1.Name,
        Description = p1.Item1.Description,
        GroupId = p1.Item2.GroupId
    };
}

// Update: Tuple to POCO
public GroupDM Main(ValueTuple<AuthorizationGroup, GroupDetails> p1, GroupDM p2)
{
    GroupDM result = p2 ?? new GroupDM();

    result.Name = p1.Item1.Name;
    result.Description = p1.Item1.Description;
    result.GroupId = p1.Item2.GroupId;
    return result;

}

// New:  POCO to Tuple
public ValueTuple<AuthorizationGroup, GroupDetails> Main(GroupDM p1)
{
    return p1 == null ? default(ValueTuple<AuthorizationGroup, GroupDetails>) : new ValueTuple<AuthorizationGroup, GroupDetails>(p1 == null ? null : new AuthorizationGroup()
    {
        Name = p1.Name,
        Description = p1.Description
    }, p1 == null ? null : new GroupDetails() {GroupId = p1.GroupId});
}

// Update:  POCO to Tuple
public ValueTuple<AuthorizationGroup, GroupDetails> Main(GroupDM p1, ValueTuple<AuthorizationGroup, GroupDetails> p2)
{
    return p1 == null ? default(ValueTuple<AuthorizationGroup, GroupDetails>) : new ValueTuple<AuthorizationGroup, GroupDetails>(p1 == null ? null : new AuthorizationGroup()
    {
        Name = p1.Name,
        Description = p1.Description
    }, p1 == null ? null : new GroupDetails() {GroupId = p1.GroupId});
}

Thanks in advance,

Chris

DocSvartz commented 9 months ago

Hello, named tuple, not named before compile. then always == Tuple<type1, type2> with tipe1 item1, type2 item2. You mapping for name, but name Not achievable, including for reflection, and after compile. Name use only when development type helpers

DocSvartz commented 9 months ago

Hello @ChristoGC If You get This Result?

[TestMethod]
public void MappedNamedTuple()
{
    TypeAdapterConfig<GroupDM604, (AuthorizationGroup604 authorizationGroup, GroupDetails604 groupDetails)>
    .NewConfig()
    .Map(dest => dest.authorizationGroup, src => src)
    .Map(dest => dest.groupDetails, src => src);

    GroupDM604 groupDM = new() { Name = "Hello world", Description = "Jon Bon Jovi" };

    // Create a new instance of (AuthorizationGroup, GroupDetails): This works
    var tuple = groupDM.Adapt<(AuthorizationGroup604 authorizationGroup, GroupDetails604 groupDetails)>();

    // Now lets update poco
    groupDM.GroupId = 99;
    groupDM.Description = "Led Zep";

    //Now lets update tuple: Doesn't work
    groupDM.Adapt(tuple);

    tuple.groupDetails.GroupId.ShouldBe(99); // true
    tuple.authorizationGroup.Description.ShouldBe("Led Zep"); // true 
}