Mapster
Mapster copied to clipboard
Mapster.Tool - Error generating two-way mappings with init properties.
Best to explain the issue with an eg:
Below is my domain model
public class SalesOrder
{
public Guid Id { get; init; }
public decimal Total { get; init; }
public string Status { get; init; }
}
and I am trying to create a corresponding Dto using the Mapster.Tool
<Target Name="Mapster">
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster model -a "$(TargetDir)$(ProjectName).dll" -n "Mapster.TestBench.Dtos" -o "Dtos/Generated"" />
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster extension -a "$(TargetDir)$(ProjectName).dll" -n "Mapster.TestBench.DtoMapper" -o "Dtos/Generated"" />
</Target>
The Dto object gets generated as expected.
public partial class SalesOrderDto
{
public Guid Id { get; set; }
public decimal Total { get; set; }
public string Status { get; set; }
}
But the mapper code generated doesn't compile.
public static SalesOrder AdaptTo(this SalesOrderDto p2, SalesOrder p3)
{
if (p2 == null)
{
return null;
}
SalesOrder result = p3 ?? new SalesOrder();
//===========================================
// Id, Total & Status are init properties in the SaleOrder type and fails to compile.
result.Id = p2.Id;
result.Total = p2.Total;
result.Status = p2.Status;
//===========================================
return result;
}
It is in this second variation of Adapt method that takes in both an Dto & Entity object, and tries to update the Entity object with the values from Dto object.
The other method generated works as expected.
public static SalesOrder AdaptToSalesOrder(this SalesOrderDto p1)
{
return p1 == null ? null : new SalesOrder()
{
Id = p1.Id,
Total = p1.Total,
Status = p1.Status
};
}
If this is a bigger piece of work, even a flag to avoid creating the second method will be an interim fix.
My CodeGenerationRegister as below if it helps.
public class ModelGenerationRegister : ICodeGenerationRegister
{
public void Register(CodeGenerationConfig config)
{
config.AdaptTwoWays($"[name]Dto")
.ForAllTypesInNamespace(typeof(SalesOrder).Assembly, "Mapster.Domain");
config.GenerateMapper("[name]Mapper")
.ForTypes(typeof(SalesOrder));
}
}
I am able to get rid of the build errors, if I do the below:
- Generate the Dtos as record types.
- Use
MapToConstructor(true)
in my CodeGenerationRegister.
This generates the code below, which while incorrect still compiles.
public static SalesOrder AdaptTo(this SalesOrderDto p2, SalesOrder p3)
{
if (p2 == null)
{
return null;
}
SalesOrder result = p3 ?? new SalesOrder();
return result;
}
Changes made from above:
Added the -r flag to the mapster model command.
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet mapster model **-r** -a "$(TargetDir)$(ProjectName).dll" -n "Mapster.TestBench.Dtos" -o "Dtos/Generated"" />
Added MapToConstructor(true) in the register below.
public class ModelGenerationRegister : ICodeGenerationRegister
{
public void Register(CodeGenerationConfig config)
{
config.AdaptTwoWays($"[name]Dto")
.MapToConstructor(true)
.ForAllTypesInNamespace(typeof(SalesOrder).Assembly, "Mapster.Domain");
config.GenerateMapper("[name]Mapper")
.ForTypes(typeof(SalesOrder));
}
}
However, the casing issues with the record property names #358 doesn't make this a usable workaround.
@DonnelCyril I've pushed prerelease packages to NuGet for Mapster, Mapster.Core and Mapster.Tool. Could you please try Mapster.Tool version 8.4.0-pre1 and see how that works for you?
Hey @andrerav, looks like the prerelease fixed the casing issue for record types. But the mapper generated changes after I upgraded to pre-release and doesn't compile as before.
This is the mapper tool with version 8.3.0, Mapster version 7.3.0
public static partial class SalesOrderMapper
{
public static SalesOrder AdaptToSalesOrder(this SalesOrderDto p1)
{
return p1 == null ? null : new SalesOrder() {};
}
public static SalesOrder AdaptTo(this SalesOrderDto p2, SalesOrder p3)
{
if (p2 == null)
{
return null;
}
SalesOrder result = p3 ?? new SalesOrder();
return result;
}
public static SalesOrderDto AdaptToDto(this SalesOrder p4)
{
return p4 == null ? null : new SalesOrderDto(p4.Id, p4.Total, p4.Status);
}
public static SalesOrderDto AdaptTo(this SalesOrder p5, SalesOrderDto p6)
{
return p5 == null ? null : new SalesOrderDto(p5.Id, p5.Total, p5.Status);
}
}
This is the mapper with version 8.4.0-pre01, Mapster version 7.4.0-pre01
public static partial class SalesOrderMapper
{
public static SalesOrder AdaptToSalesOrder(this SalesOrderDto p1)
{
return p1 == null ? null : new SalesOrder()
{
Id = p1.Id,
Total = p1.Total,
Status = p1.Status
};
}
public static SalesOrder AdaptTo(this SalesOrderDto p2, SalesOrder p3)
{
if (p2 == null)
{
return null;
}
SalesOrder result = p3 ?? new SalesOrder();
result.Id = p2.Id;
result.Total = p2.Total;
result.Status = p2.Status;
return result;
}
public static SalesOrderDto AdaptToDto(this SalesOrder p4)
{
return p4 == null ? null : new SalesOrderDto(p4.Id, p4.Total, p4.Status);
}
public static SalesOrderDto AdaptTo(this SalesOrder p5, SalesOrderDto p6)
{
return p5 == null ? null : new SalesOrderDto(p5.Id, p5.Total, p5.Status);
}
}
Well, that was certainly unexpected. Besides the small change I made in ExpressionTranslator the only change to the codebase since 7.3.0/8.3.0 is pull request #415. I can't immediately tell if that is the cause, but I will temporarily revert #415 and push another prerelease package for you at lunch time today (around 10:00 GMT).
@DonnelCyril I pushed prerelease packages again now. Might take a few minutes until they are indexed. Can you give it another try and see if the problem persists? Also thank you very much for your patience and help! :)
@DonnelCyril Can you confirm if the problem went away or still persists?
I'm using Mapster.Tool 8.4.0-pre05 and I can confirm this issue still persists.
@andrerav If Problem is this. But it is Update function
From RunTime Update. Only the runtime update mechanism itself has been removed. Probably it simply cannot be used outside of runtime code. - this code included in generate mapper
Or the reverse update code was inserted here. Because in the created function Adapt it looks like this:
$result = ($var2 ?? .New Mapster.Tests.SalesOrderDto());
.Block() {
$result.Id = $var1.Id;
$result.Total = $var1.Total;
$result.Status = $var1.Status
};
Hello @andrerav , @DonnelCyril
This is related to the function description upd It should be like the Adaptation Function - not that's not the reason
I can not get this behavior from init property. But this problem transform in this #633 from non public Setters
public class SalesOrder
{
public SalesOrder(){}
SalesOrder(Guid id, decimal total, string status)
{
Id = id;
Total = total;
Status = status;
}
public Guid Id { get; protected set; }
public decimal Total { get; protected set; }
public string Status { get; protected set; }
}