MapsterMapper / Mapster

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

Mapster.tool MapToConstructor reassigns a object property despite assigning it in the constructor #633

Closed carumsoft closed 9 months ago

carumsoft commented 9 months ago

Register

public class FooRegister : IRegister
{
    public void Register(TypeAdapterConfig config)
    {
        config.NewConfig<FooDto, Foo>().MapToConstructor(true);
    }
}

Dto class:

public class FooDto
{
    public string? Name { get; set; }

    public bool Status { get; set; } = true;
}

Entity class with constructor

public class Foo
{
    public Name Name { get; private set; }
    public bool Status { get; protected set; }

    public Foo(Name name, bool status)
    {
        Name = name;
        Status = status;
    }
}

Generated mapper:

public Foo MapToEntity(FooDto p6)
{
    return p6 == null ? null : new Foo(new Name(p6.Name), p6.Status)
    {
        Name = new Name(p6.Name), <<<<---- THIS IS NOT NEEDED
    };
}
DocSvartz commented 9 months ago

Hello @carumsoft , please add an example of type Name. It is important that count of Constructors and their access rights.

DocSvartz commented 9 months ago

I got similar behavior using Map.Tool 8.40 from this

public class Foo
{
    public Foo() { }
    public Name Name { get; private set; }
    public bool Status { get; protected set; }

    public Foo(Name name, bool status)
    {
        Name = name;
        Status = status;
    }
}

public class Name
{
    public Name(string input)
    {
        Value = input;
    }

    public string Value { get; set; }
}

Description :

IF SourceType have public Property {get; set;} name == name of this input paramtrer in Constructor params DestinationType Implements this behavior. From First match in alphabetical order. (Name==name).

This should work as expected:

public class Foo
{
    public Name Name { get; private set; }
    public bool Status { get; protected set; }

    public Foo(Name inputName, bool InputStatus)
    {
        Name = inputName;
        Status = InputStatus;
    }
}

@carumsoft Check this please

carumsoft commented 9 months ago

Yes, when i have same names of variable in constructor and dto and different from property names, it works, simply by not finding property.

public class Foo
{
    public Name Name { get; private set; }
    public bool Status { get; protected set; }

    public Foo(Name inputName, bool status)
    {
        Name = inputName;
        Status = Status; <<<<<<-------- it works with bool, no need different names
    }
}

public class FooDto
{
    public string? InputName { get; set; }

    public bool Status { get; set; } = true;
}

public Foo MapToEntity(FooDto p6)
{
    return p6 == null ? null : new Foo(new Name(p6.InputName), p6.Status)
    { }; <<<<<<<<-------- empty curly brackets
}
carumsoft commented 9 months ago

What if I don't want to change the names in the dto files? This workaround is awful.

DocSvartz commented 9 months ago

Yes it is, I wasn't suggesting this as a solution. I just wanted to make sure I was going in the right direction. Map the constructor yourself and disable the rest of the mapping

.ConstructUsing((src,dest)=> new SomeFoo(new Name(src.SecondName),src.Status))
.IgnoreNonMapped(true)
DocSvartz commented 9 months ago

@carumsoft If Foo is Your class - consider making it a record and remove public constructor without parametrs

public record Foo 
{

    public Name Name { get; private set; }
    public bool Status { get; protected set; }

    public Foo(Name Name, bool Status)
    {
        this.Name = Name;
        this.Status = Status;
    }
}

This is strange, but for me the specified behavior occurs only when Foo have a public constructor without parameters ()

carumsoft commented 9 months ago

.ConstructUsing((src,dest)=> new SomeFoo(new Name(src.SecondName),src.Status)) .IgnoreNonMapped(true)

Yes, i can do that, but there is no gain, it is like writing own mapper. Thank you for your contribution to my problem.

carumsoft commented 9 months ago

consider making it a record and remove public constructor without parametrs

Foo is an entity, so need to be a class and Foo need to have constructor without parameters for Entity Framework.

DocSvartz commented 9 months ago

@carumsoft I think this will help you))

.IgnoreMember((member, side) => side == MemberSide.Destination
                    && member.SetterModifier != AccessModifier.Public)

In version Mapster.tool 8.4.0 the constructor is created. No properties with non-public setters

carumsoft commented 9 months ago

yes, this is very helpful. Thank you.