Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabled reference handling does not resolve user-defined mapping methods correctly #1154

Closed
ristogod opened this issue Mar 7, 2024 · 2 comments · Fixed by #1162
Closed
Labels
bug Something isn't working released on @next

Comments

@ristogod
Copy link

ristogod commented Mar 7, 2024

Mapperly is generating multiple maps for the same type, some of which honor the mapping ignores, others that occlude it.

Declaration code

namespace MapperlyTest;

[Mapper(UseReferenceHandling = true)]
public partial class Mapper
{
    public static int ToInt(bool source) => source ? 1 : 0;
   
    [MapperIgnoreSource(nameof(SourceC.Name))]
    [MapperIgnoreTarget(nameof(TargetC.Name))]
    public partial TargetC ToTargetFrom(SourceC source);

    public partial TargetA ToTargetFrom(SourceA source);

    public partial TargetB ToTargetFrom(SourceB source);
}

public class SourceA
{
    public int Id { get; set; }
}

public class SourceB
{
    public virtual ICollection<SourceC> CList { get; set; } = [];
    public int Id { get; set; }
}

public class SourceC
{
    public Guid Id { get; set; }
    public string? Name { get; set; }
}

public class TargetA
{
    public int Id { get; set; }
}

public class TargetB
{
    public ICollection<TargetC> CList { get; set; } = [];
    public required int Id { get; set; }
}

public class TargetC
{
    public required Guid Id { get; set; }
    public string? Name { get; set; }
}

Actual generated code

namespace MapperlyTest;

public partial class Mapper
    {
        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        public partial global::MapperlyTest.TargetC ToTargetFrom(global::MapperlyTest.SourceC source)
        {
            return MapToTargetC1(source, new global::Riok.Mapperly.Abstractions.ReferenceHandling.PreserveReferenceHandler());
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        public partial global::MapperlyTest.TargetA ToTargetFrom(global::MapperlyTest.SourceA source)
        {
            return MapToTargetA(source, new global::Riok.Mapperly.Abstractions.ReferenceHandling.PreserveReferenceHandler());
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        public partial global::MapperlyTest.TargetB ToTargetFrom(global::MapperlyTest.SourceB source)
        {
            return MapToTargetB(source, new global::Riok.Mapperly.Abstractions.ReferenceHandling.PreserveReferenceHandler());
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        private global::MapperlyTest.TargetB MapToTargetB(global::MapperlyTest.SourceB source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
        {
            if (refHandler.TryGetReference<global::MapperlyTest.SourceB, global::MapperlyTest.TargetB>(source, out var existingTargetReference))
                return existingTargetReference;
            var target = new global::MapperlyTest.TargetB()
            {
                Id = source.Id,
            };
            refHandler.SetReference<global::MapperlyTest.SourceB, global::MapperlyTest.TargetB>(source, target);
            target.CList = MapToList(source.CList, refHandler);
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        private global::MapperlyTest.TargetA MapToTargetA(global::MapperlyTest.SourceA source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
        {
            if (refHandler.TryGetReference<global::MapperlyTest.SourceA, global::MapperlyTest.TargetA>(source, out var existingTargetReference))
                return existingTargetReference;
            var target = new global::MapperlyTest.TargetA();
            refHandler.SetReference<global::MapperlyTest.SourceA, global::MapperlyTest.TargetA>(source, target);
            target.Id = source.Id;
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        private global::MapperlyTest.TargetC MapToTargetC(global::MapperlyTest.SourceC source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
        {
            if (refHandler.TryGetReference<global::MapperlyTest.SourceC, global::MapperlyTest.TargetC>(source, out var existingTargetReference))
                return existingTargetReference;
            var target = new global::MapperlyTest.TargetC()
            {
                Id = source.Id,
            };
            refHandler.SetReference<global::MapperlyTest.SourceC, global::MapperlyTest.TargetC>(source, target);
            target.Name = source.Name;
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        private global::System.Collections.Generic.List<global::MapperlyTest.TargetC> MapToList(global::System.Collections.Generic.ICollection<global::MapperlyTest.SourceC> source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
        {
            var target = new global::System.Collections.Generic.List<global::MapperlyTest.TargetC>(source.Count);
            foreach (var item in source)
            {
                target.Add(MapToTargetC(item, refHandler));
            }
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
        private global::MapperlyTest.TargetC MapToTargetC1(global::MapperlyTest.SourceC source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
        {
            if (refHandler.TryGetReference<global::MapperlyTest.SourceC, global::MapperlyTest.TargetC>(source, out var existingTargetReference))
                return existingTargetReference;
            var target = new global::MapperlyTest.TargetC()
            {
                Id = source.Id,
            };
            refHandler.SetReference<global::MapperlyTest.SourceC, global::MapperlyTest.TargetC>(source, target);
            return target;
        }
    }

Expected generated code

namespace MapperlyTest;

public partial class Mapper
{
    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    public partial global::MapperlyTest.TargetC ToTargetFrom(global::MapperlyTest.SourceC source)
    {
        return MapToTargetC(source, new global::Riok.Mapperly.Abstractions.ReferenceHandling.PreserveReferenceHandler());
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    public partial global::MapperlyTest.TargetA ToTargetFrom(global::MapperlyTest.SourceA source)
    {
        return MapToTargetA(source, new global::Riok.Mapperly.Abstractions.ReferenceHandling.PreserveReferenceHandler());
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    public partial global::MapperlyTest.TargetB ToTargetFrom(global::MapperlyTest.SourceB source)
    {
        return MapToTargetB(source, new global::Riok.Mapperly.Abstractions.ReferenceHandling.PreserveReferenceHandler());
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    private global::MapperlyTest.TargetB MapToTargetB(global::MapperlyTest.SourceB source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
    {
        if (refHandler.TryGetReference<global::MapperlyTest.SourceB, global::MapperlyTest.TargetB>(source, out var existingTargetReference))
            return existingTargetReference;
        var target = new global::MapperlyTest.TargetB()
        {
            Id = source.Id,
        };
        refHandler.SetReference<global::MapperlyTest.SourceB, global::MapperlyTest.TargetB>(source, target);
        target.CList = MapToList(source.CList, refHandler);
        return target;
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    private global::MapperlyTest.TargetA MapToTargetA(global::MapperlyTest.SourceA source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
    {
        if (refHandler.TryGetReference<global::MapperlyTest.SourceA, global::MapperlyTest.TargetA>(source, out var existingTargetReference))
            return existingTargetReference;
        var target = new global::MapperlyTest.TargetA();
        refHandler.SetReference<global::MapperlyTest.SourceA, global::MapperlyTest.TargetA>(source, target);
        target.Id = source.Id;
        return target;
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    private global::MapperlyTest.TargetC MapToTargetC(global::MapperlyTest.SourceC source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
    {
        if (refHandler.TryGetReference<global::MapperlyTest.SourceC, global::MapperlyTest.TargetC>(source, out var existingTargetReference))
            return existingTargetReference;
        var target = new global::MapperlyTest.TargetC()
        {
            Id = source.Id,
        };
        refHandler.SetReference<global::MapperlyTest.SourceC, global::MapperlyTest.TargetC>(source, target);
        return target;
    }

    [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "3.4.0.0")]
    private global::System.Collections.Generic.List<global::MapperlyTest.TargetC> MapToList(global::System.Collections.Generic.ICollection<global::MapperlyTest.SourceC> source, global::Riok.Mapperly.Abstractions.ReferenceHandling.IReferenceHandler refHandler)
    {
        var target = new global::System.Collections.Generic.List<global::MapperlyTest.TargetC>(source.Count);
        foreach (var item in source)
        {
            target.Add(MapToTargetC(item, refHandler));
        }
        return target;
    }
}

Environment (please complete the following information):

  • Mapperly Version: 3.4.0
  • .NET Version: .NET 8.0.201
  • Target Framework: net8.0
  • Compiler version: 4.9.0-3.24121.1 (a98c90d5)
  • C# Language Version: 12.0
  • IDE: Visual Studio v17.9.2
  • OS: Windows 11 Pro 23H2 22631.3155

Additional context
This issue was reported to me from a offshore developer and coworker. Here is the relevant information provided to me by him:

This problem only occurs when the Mapper is defined as it is now. If I delete one method, for example, delete ToInt, or adjust the order of methods, for example, move public partial TargetC ToTargetFrom(SourceC source); to the end of the class file, the generated code is correct, only one MapToTargetC method is generated. This is obviously not normal.

So I think this should be a bug in Mapperly.

@ristogod ristogod added the bug Something isn't working label Mar 7, 2024
@latonz
Copy link
Contributor

latonz commented Mar 8, 2024

Thanks for reporting.

On a first glance this looks like Mapperly may not resolve the user-defined mappings correctly when reference handling is enabled: TargetC ToTargetFrom(SourceC) is not resolved when generating the mapping from SourceB.CList to TargetB.CList. This leads to a new mapping named MapToTargetC which even includes the mapping of the name (since the C mapping was not resolved, the user configuration on the mapping method was not resolved either).

I'll look into it as soon as I find the time.

As a workaround it may work if you disable reference handling or add a [ReferenceHandler] IReferenceHandler refHandler parameter to all mapping methods and provide an implementation yourself.

@latonz latonz changed the title Multiple Maps Generated for same type, some correct, some incorrect Enabled reference handling does not resolve user-defined mapping methods correctly Mar 8, 2024
Copy link

🎉 This issue has been resolved in version 3.5.0-next.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working released on @next
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants