riok / mapperly

A .NET source generator for generating object mappings. No runtime reflection.

Home Page:https://mapperly.riok.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to use constructor mapping and properties' mapping simultaneously

Liero opened this issue · comments

Is your feature request related to a problem? Please describe.

It seems that there is not way to generate mapping for class, which takes source object as a parameter and has required properties.

public class Source
{
    public string A { get; set; }
}

public class Target
{
    public Target(Foo foo)
    {        
    }

    public required string A { get; set; }
}

Following mapper does not map property A

[Mapper]
public partial class MyMapper
{
    public partial  Target Map(Source source);
}

which causes compiler error CS9035: Required member 'Target.A' must be set in the object initializer or attribute constructor.

Describe the solution you'd like
Map required properties if ctor is not annotated with [SetsRequiredMembers]

Describe alternatives you've considered
Add mapping strategy option, whether to set properties (required or not) even if there is ctor.

commented

I'm not sure if I understand the issue correctly...
In the title of the issue you mention User constructor mapping what are you referring to with this?

I fixed the title, it was a typo.
The issue is that the library allows to map either constructor parameters or properties, but not both. If the property is required, it is a problem.

commented

@Liero I added a test with what I understand doesn't work according to this issue, but it seems to work fine as expected: #1243.
Could you maybe provide a full minimal repro?

Sorry for delayed answer.
Is it possible that this only works in a version that has not been released yet?
Here is output I'm getting:

public partial class MyMapper
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.5.1.0")]
    public partial global::MyProject.Target Map(global::MyProject.Source source)
    {
        return new global::MyProject.Target(source);
    }
}

Full source available here: https://dotnetfiddle.net/WDJkP3

commented

Got it, this is actually expected behaviour. Mapperly supports several conversions. Only the first one that is able to convert the source object into the target object is used. In your case thats the "Constructor" conversion, the "New Instance" conversion is never applied. Your use-case (accepting the entire source object as the only target ctor parameter while also mapping other members) is not supported by Mapperly.

Well, I had this use case and had to opt out of using mapperly because of this.

The use case was to map domain object to a viewmodel, but also keep the original domain object instance, because it contained a lot of business logic that I didn't want to reinvent in viewmodels.

commented

You could disable the constructor conversion (see docs), and use the new MapPropertyFromSource for the constructor (see docs).