首页 > 解决方案 > 如何使用 AutoMapper 将嵌套的 json 对象映射到具有多个自定义类的 POCO?

问题描述

我尝试了几个与我的问题相关的已回答问题,但我似乎无法解决问题。

这是我发送到我的 API 的 JSON 对象:

{
    "userName": "Test_06",
    "password": "@00a00B00c",
    "firstName": "Test",
    "lastName": "Test",
    "address": {
        "houseNumber": 1,
        "appartementBus": "A bus 34",
        "street": "Teststreet",
        "ZIP": "0000",
        "country": "Test"
    }
}

我正在尝试将地址对象映射到地址 POCO:

using System.Collections.Generic;

namespace Kubex.Models
{

  public class Address
  {
      public int Id { get; set; }
      public int HouseNumber { get; set; }
      public string AppartementBus { get; set; }

      public int StreetId { get; set; }
      public virtual Street Street { get; set; }
      public int ZIPId { get; set; }
      public virtual ZIP ZIP { get; set; }
      public int CountryId { get; set; }
      public virtual Country Country { get; set; }

      public virtual ICollection<User> Users { get; set; }
      public virtual ICollection<Company> Companies { get; set; }
      public virtual ICollection<Post> Posts { get; set; }
  }
}

以下类型基本相同,国家的例子看起来像

using System.Collections.Generic;

    namespace Kubex.Models
    {
        public class Country
        {
            public byte Id { get; set; }
            public string Name { get; set; }

            public virtual ICollection<Address> Addresses { get; set; }
        }
    }

我从邮递员那里得到的错误如下:

{
    "errors": {
        "address.ZIP": [
            "Error converting value \"0000\" to type 'Kubex.Models.ZIP'. Path 'address.ZIP', line 10, position 18."
        ],
        "address.street": [
            "Error converting value \"Teststreet\" to type 'Kubex.Models.Street'. Path 'address.street', line 9, position 27."
        ],
        "address.country": [
            "Error converting value \"Test\" to type 'Kubex.Models.Country'. Path 'address.country', line 11, position 22."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|cca8afa7-463e762ee0ed670d."
}

我尝试为每个类型创建映射,从字符串到类型。但我不断收到错误。看起来像这样:还尝试了 ConstructUsing。

    CreateMap<string, Country>()
        .ConvertUsing(s => new Country { Name = s });

    CreateMap<string, ZIP>()
        .ConvertUsing(s => new ZIP { Code = s });

    CreateMap<string, Street>()
        .ConvertUsing(s => new Street { Name = s });

我是否也需要为地址创建地图?如果是这样,我应该怎么做,因为我不知道它如何看待对象,以及我应该如何为它创建地图。

提前谢谢了。

编辑:

这是我发送 API 端点以注册用户的 DTO 的一部分,这是使用的映射:

CreateMap<UserRegisterDTO, User>();

这就是 DTO 的样子:

using Kubex.Models;

namespace Kubex.DTO
{
    public class UserRegisterDTO
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Address Address { get; set; }
    }
}

这是 User 类的样子:

using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;

namespace Kubex.Models
{
    public class User : IdentityUser
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EmployeeNumber { get; set; }

        public int? AddressId { get; set; }
        public virtual Address Address { get; set; }

        public virtual ICollection<License> Licenses { get; set; }
        public virtual ICollection<Contact> Contacts { get; set; }
        public virtual ICollection<Post> Posts { get; set; }
    }
}

在我的服务中,我使用这一行来进行映射:

var newUser = _mapper.Map<User>(dto);

标签: c#json.net-coremappingautomapper

解决方案


如果我理解正确,UserRegisterDTO 中使用的 Address 类与 User 中的相同。并不是说这直接是一个问题,但从架构的角度来看,当您将 DTO 与数据库模型(我猜)混合时,DTO 应该只引用简单类型或其他 DTO。

因此,我宁愿有这样的东西:

public class UserRegisterDTO
{
   public string UserName { get; set; }
   public string Password { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public AddressDTO Address { get; set; }
}

public class AddressDTO {
   public string HouseNumber { get;set; }
   public string AppartementBus { get;set; }
   public string Street { get;set; }
   public string ZIP { get;set; }
   public string Country { get;set; }
}

然后可能从 AddressDTO 映射到地址:

   CreateMap<AddressDTO, Address>()
     .ForMember(d => d.Country, d => d.MapFrom(s => new Country { Name = s.Country }))
  // cut for brevity

但我还是不喜欢这个。由于此映射会为每个请求创建国家/地区实例,并且根据您的设置,它甚至可能在数据库中创建新行。在现有国家中查找国家可能会更好?

您可以通过映射上下文传递国家/地区实例列表并在 ResolveUsing 中使用,但我个人不喜欢在 AutoMapper 映射中使用复杂的逻辑,因为这会将业务逻辑分散在至少两个地方并使单元测试复杂化。

Automapper 非常适合做简单的事情,但如果你觉得它被推到了极限,我认为最好至少手动完成部分映射。


推荐阅读