MapsterMapper / Mapster

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

Mapping Properties of different types, No default constructor for type 'CultureInfo', please use 'ConstructUsing' or 'MapWith' #573

Open codelovercc opened 1 year ago

codelovercc commented 1 year ago

Hi,

I use Mapster with version 7.3.0 and .Net 7.0.1

I have two classes.

public class Entity
{
    public Guid Id { get; set; }

    public string Content { get; set; } = default!;

    public string Note { get; set; } = default!;

    public CultureInfo CultureInfo { get; set; } = default!;
}

public class Dto
{
    [Required, StringLength(64)]
    public string Id { get; set; } = default!;

    [Required, StringLength(4096)]
    public string Content { get; set; } = default!;

    [Required, StringLength(1024)]
    public string Note { get; set; } = default!;

    [Required]
    public string CultureName { get; set; } = default!;
}

And with mapster config

TypeAdapterConfig<Dto, Entity>.NewConfig()
            .Map(entity => entity.CultureInfo, dto => CultureInfo.GetCultureInfo(dto.CultureName));
TypeAdapterConfig<Entity, Dto>.NewConfig()
            .Map(dto => dto.CultureName, entity => entity.CultureInfo.Name);

Now execute this code

//Mapster configuration is applied.

var dto = new Dto
        {
            Id = Guid.NewGuid().ToString(),
            Content = "this is a test content",
            Note = "for test",
            CultureName = "zh-CN"
        };
var mLocal = dto.Adapt<Entity>();

var mLocal = dto.Adapt(); throws a Mapster.CompileException: Error while compiling.

Mapster.CompileException: Error while compiling

Mapster.CompileException
Error while compiling
source=MindOcean.Models.Common.MessageTemplateLocalizerDto
destination=MindOcean.Data.Entities.MessageTemplateLocalizer
type=Map
   at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)
   at Mapster.TypeAdapterConfig.CreateMapExpression(TypeTuple tuple, MapType mapType)
   at Mapster.TypeAdapterConfig.CreateDynamicMapExpression(TypeTuple tuple)
   at Mapster.TypeAdapterConfig.<GetDynamicMapFunction>b__66_0[TDestination](TypeTuple tuple)
   at Mapster.TypeAdapterConfig.<>c__DisplayClass55_0`1.<AddToHash>b__0(TypeTuple types)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Mapster.TypeAdapterConfig.AddToHash[T](ConcurrentDictionary`2 hash, TypeTuple key, Func`2 func)
   at Mapster.TypeAdapterConfig.GetDynamicMapFunction[TDestination](Type sourceType)
   at Mapster.TypeAdapter.Adapt[TDestination](Object source, TypeAdapterConfig config)
   at Mapster.TypeAdapter.Adapt[TDestination](Object source)
   at MindOcean.Test.UnitTest.MapsterTester.TestTwoWay() in /Test/MapsterTester.cs:line 71
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

Mapster.CompileException
Error while compiling
source=System.Globalization.CultureInfo
destination=System.Globalization.CultureInfo
type=Map
   at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)
   at Mapster.TypeAdapterConfig.CreateInlineMapExpression(Type sourceType, Type destinationType, MapType mapType, CompileContext context, MemberMapping mapping)
   at Mapster.Adapters.BaseAdapter.CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping mapping, Expression destination)
   at Mapster.Adapters.BaseAdapter.CreateAdaptExpression(Expression source, Type destinationType, CompileArgument arg, MemberMapping mapping, Expression destination)
   at Mapster.Adapters.ClassAdapter.CreateInlineExpression(Expression source, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateInlineExpressionBody(Expression source, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateExpressionBody(Expression source, Expression destination, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateAdaptFunc(CompileArgument arg)
   at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)

System.InvalidOperationException
No default constructor for type 'CultureInfo', please use 'ConstructUsing' or 'MapWith'
   at Mapster.Adapters.BaseAdapter.CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
   at Mapster.Adapters.ClassAdapter.CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateInstantiationExpression(Expression source, CompileArgument arg)
   at Mapster.Adapters.ClassAdapter.CreateInlineExpression(Expression source, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateInlineExpressionBody(Expression source, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateExpressionBody(Expression source, Expression destination, CompileArgument arg)
   at Mapster.Adapters.BaseAdapter.CreateAdaptFunc(CompileArgument arg)
   at Mapster.TypeAdapterConfig.CreateMapExpression(CompileArgument arg)

No default constructor for type 'CultureInfo', please use 'ConstructUsing' or 'MapWith'

I tried use MapWith and it worked.

TypeAdapterConfig<Dto, Entity>.NewConfig()
     .MapWith(dto => new Entity
     {
         CultureInfo = CultureInfo.GetCultureInfo(dto.CultureName),
         Id = Guid.Parse(dto.Id),
         Content = dto.Content,
         Note = dto.Note,
     });
TypeAdapterConfig<Entity, Dto>.NewConfig()
            .Map(dto => dto.CultureName, entity => entity.CultureInfo.Name);

This configuration didn't work.

TypeAdapterConfig<Dto, Entity>.NewConfig()
            .Map(entity => entity.CultureInfo, dto => CultureInfo.GetCultureInfo(dto.CultureName));

dto => CultureInfo.GetCultureInfo(dto.CultureName) should work to create an instance of CultureInfo with out a constructor.

Is there something I missed or is this a bug?

JDCain commented 1 year ago

I was getting the same error when trying to use the code generator for the same type of thing when using a Geometery type from a 3rd party library. I was able to get it to work with this:

TypeAdapterConfig<Geometry, Geometry>.NewConfig()
     .ConstructUsing(x => Geometry.DefaultFactory.CreateGeometry(x));
config.GenerateMapper("[name]Mapper")
     .ForType<Agency>() ;
DocSvartz commented 10 months ago

Hello, It Working with config


 [TestMethod]
 public void MappingInsiderCultureInfo()
 {
     TypeAdapterConfig<string, CultureInfo>.NewConfig()
          .MapWith(str=> new CultureInfo(str));

     TypeAdapterConfig<Dto, Entity>.NewConfig()
     .Map(entity => entity.CultureInfo, dto => dto.CultureName);
     TypeAdapterConfig<Entity Dto>.NewConfig()
           .Map(dto => dto.CultureName, entity => entity.CultureInfo.Name);

     var dto = new Dto
     {
         Id = "edcd0ab3-a949-4127-83c0-8a33f4108f09",
         Content = "this is a test content",
         Note = "for test",
         CultureName = "zh-CN"
     };
     var mLocal = dto.Adapt<Entity>();

     mLocal.Id.ToString().ShouldBe("edcd0ab3-a949-4127-83c0-8a33f4108f09");
     mLocal.CultureInfo.Name.ShouldBe("zh-CN");
     mLocal.Content.ShouldBe("this is a test content");
     mLocal.Note.ShouldBe("for test");

 }
ruxo commented 1 week ago

I have similar issue but with a property without setting.

var x = new RealTest
{
    Word = "hello"
};
x.Adapt<Test>().Dump("Mapped!");  // No default constructor

sealed class RealTest {
    public string Word {get;set;}
}

sealed record Test(string Word){
    public bool CanCall => true;
}

Tried Ignore with TypeAdapterConfig but it doesn't work.