繁体   English   中英

自定义从源转换到目标的 AutoMapper 问题

[英]AutoMapper problem with custom convert from source to destination

我正在使用 AutoMapper 将我的 db 模型与我的 api 模型映射。 但是我对自定义映射有疑问。 我将尝试解释我的问题:

所以我有这样的 db 和 api 模型:

public class ApiModel
{
    public List<ApiSubModel> Colors { get; set; }
}

public class DbModel
{
    public string Type { get; set; }
    public string Settings { get; set; }
}

通常DbModel Settings属性是ApiModel的序列化版本。 所以我想在创建映射时通过自定义转换来实现这一点:

启动.cs:

services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies())

映射器配置文件类:

internal class DbToApiMapping : Profile
    {
        public DbToApiMapping()
        {
            CreateMap<ApiModel, DbModel>()
                .ConvertUsing((source, dest, context) => new DbModel
                {
                    Type = context.Items["Type"].ToString(),
                    Settings = JsonConvert.SerializeObject(source)
                });
                

            CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) => 
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return new ApiModel
                    {
                        Colors = res.Colors
                    };
                });


        }


    }

对于第一张地图,我这样使用它:

var settings = modelMapper.Map<DbModel>(req.Settings, opt => opt.Items["Type"] = "Setpoint");

对于第二张地图,我这样使用它:

var ss = modelMapper.Map<ApiModel>(settings.Settings);

尝试映射时出现的错误如下:

Message: 
AutoMapper.AutoMapperMappingException : Missing type map configuration or unsupported mapping.

Mapping types:
Object -> ApiModel
System.Object -> CommonLibrary.Models.ApiModel

我确定我做错了什么......但我无法完全理解到底该看什么。 对于我尝试使用.ConvertUsing()方法的第二个映射,但错误是相同的。

有人可以帮忙解决这个问题。

提前致谢

朱利安

--- 编辑---正如我在没有 DI 的情况下尝试的评论中所建议的那样。 这是代码:

class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration( cfg =>
            {
                cfg.CreateMap<ApiModel, DbModel>()
                .ConvertUsing((source, dest, context) => new DbModel
                {
                    Type = context.Items["Type"].ToString(),
                    Settings = JsonConvert.SerializeObject(source)
                });
                cfg.CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) =>
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return new ApiModel
                    {
                        Colors = res.Colors
                    };
                });
            });

            var modelMapper = config.CreateMapper();

            var apiObj = new ApiModel
            {
                Colors = new List<ApiSubModel>
                {
                    new ApiSubModel
                    {
                        SomeProp = "Test",
                        SubModel = new ApiSubSubModel
                        {
                            IntProp = 3,
                            StringProp = "Alabala"
                        }
                    }
                }
            };
            DbModel dbRes = modelMapper.Map<DbModel>(apiObj, opt => opt.Items["Type"] = "Setpoint");

            var dbObj = new DbModel
            {
                Type = "Setpoint",
                Settings = "{\"Colors\":[{\"SomeProp\":\"Test\",\"SubModel\":{\"IntProp\":3,\"StringProp\":\"Alabala\"}}]}"
            };

            var apiRes = modelMapper.Map<ApiModel>(dbObj);
            Console.WriteLine(dbRes.Settings);
            Console.WriteLine(apiRes.Colors[0].SomeProp);
            Console.WriteLine(apiRes.Colors[0].SubModel.StringProp);
            Console.ReadLine();
        }
    }

    public class ApiModel
    {
        public List<ApiSubModel> Colors { get; set; }
    }

    public class DbModel
    {
        public string Type { get; set; }
        public string Settings { get; set; }
    }

    public class ApiSubModel
    {
        public string SomeProp { get; set; }
        public ApiSubSubModel SubModel { get; set; }
    }

    public class ApiSubSubModel
    {
        public int IntProp { get; set; }
        public string StringProp { get; set; }
    }

它正在工作,但是有些奇怪,当我想调试程序并在var apiRes = modelMapper.Map<ApiModel>(dbObj);之后放置一个断点时并尝试调试apiRes的值,它说apiRes error CS0103: The name 'apiRes' does not exist in the current context

我稍微调整了你的代码,现在它可以工作了(虽然我必须使用我自己的 JSON 和我自己的 SubApiModel)但是如果你不确定,你可以在评论中问我

我的模型

public class ApiModel
    {
        public List<ApiSubModel> Colors { get; set; }
    }

    public class ApiSubModel
    {
        public string Name { get; set; }
    }

    public class DbModel
    {
        public DbModel(string type, string settings)
        {
            Type = type;
            Settings = settings;
        }

        public string Type { get; set; }
        public string Settings { get; set; }
    }

和我的 JSON

{
  Colors: 
  [
     {
        "Name": "AMunim"
     }
   ]
}

这是我的映射配置:

.CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) =>
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return res;
                });

这基本上反序列化了整个 JSON,并且由于它有一个属性 Color,它是 ApiSubModel 的列表,我可以简单地将整个字符串转换为 Object(ApiModel 类型)。

这是我完整的测试代码

using AutoMapper;
using Newtonsoft.Json;
using StackAnswers;
using StackAnswers.Automapper;
using System.Numerics;

DbModel dbModel = new DbModel("very important type", "{Colors: [{\"Name\": \"AMunim\"}]}");
MapperConfiguration config = new(cfg =>
{
    cfg.CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) =>
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return res;
                });
});


Mapper mapper = new(config);

ApiModel apiModel = mapper.Map<DbModel, ApiModel>(dbModel);

Console.WriteLine(apiModel.Colors.Count);
Console.WriteLine(apiModel.Colors.FirstOrDefault()?.Name);
Console.Read();

和输出: 证明它有效的证据

编辑您可以单独注册您的配置文件/映射并强制 DI 使用它,即注册

var config = new MapperConfiguration(c => {
    //profile
    c.AddProfile<DbToApiMapping>();
    //mappings
    c.CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) =>
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return res;
                });

});
//now register this instance
services.AddSingleton<IMapper>(s => config.CreateMapper());

现在,当您请求此服务时,您将获得在您的 ctor 中应用配置的实例

public class BadClass
{
    private readonly IMapper _mapper;
    BadClass(IMapper mapper)
    {
         _mapper = mapper;
    }


    public void HandleFunction()
    {
       //here you can use this
       ApiModel apiModel = _mapper.Map<DbModel, ApiModel>(dbModel);
       
    }
}

我设法找到了我的错误,我不得不承认这是一个愚蠢的错误,但我花了很多时间来弄清楚。 问题在于var ss = modelMapper.Map<ApiModel>(settings.Settings); 看到我有这样的Profile

CreateMap<DbModel, ApiModel>()
                .ConstructUsing((source, context) => 
                {
                    var res = JsonConvert.DeserializeObject<ApiModel>(source.Settings);
                    return new ApiModel
                    {
                        Colors = res.Colors
                    };
                });

它期望源是一个DbModel对象,但实际上我传递了这个对象的一个​​属性,它实际上是一个字符串。 而且我没有定义那种映射,这就是我得到错误的原因。

正确的用法必须是: var ss = modelMapper.Map<ApiModel>(settings);

所以感谢您的所有建议!

问候,朱利安

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM