使用 AutoMapper 映射两个模型

Posted

技术标签:

【中文标题】使用 AutoMapper 映射两个模型【英文标题】:Mapping two models with AutoMapper 【发布时间】:2021-12-23 01:57:35 【问题描述】:

我有一个 Customer 模型,它与 Address 模型具有一对一的关系。我正在使用 AutoMapper 来映射这些值。我想使用我的 API 在数据库中创建一个新客户,理想情况下,它将根据提供给客户模型的信息插入一个新地址行。

这是我收到的实体框架核心的 SQL 异常: ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'AddressId', table 'dbo.Customers'; column does not allow nulls. INSERT fails.

注意 - BaseEntity 仅包含 ID、并发检查等常见属性

客户模型:

public class Customer : BaseEntity

   public string FirstName  get; set; 
   public string LastName  get; set; 
   public string Phone  get; set; 
   public string Email  get; set; 
   public string UpdatedBy  get; set; 
   public DateTime? LastContact  get; set; 

   #region Navigation Properties
   public int? AddressId  get; set; 
   public string UserId  get; set; 
   public User User  get; set; 
   public virtual Address Address  get; set; 
   public virtual ICollection<Invoice> Invoices  get; set; 
   #endregion

我的地址模型:

public class Address : BaseEntity

   public string AddressLine1  get; set; 
   public string AddressLine2  get; set; 
   public string City  get; set; 
   public string State  get; set; 
   public string PostalCode  get; set; 

   #region Navigation Properties
   public virtual Customer Customer  get; set; 
   #endregion

我对请求使用了一个 DTO 类,如下:

public class CustomerCreateDto

   public string FirstName  get; set; 
   public string LastName  get; set; 
   public string Phone  get; set; 
   public string Email  get; set; 
   public string AddressLine1  get; set; 
   public string AddressLine2  get; set; 
   public string City  get; set; 
   public string State  get; set; 
   public string PostalCode  get; set; 
   public string UserId  get; set; 

AddressDto:

public class AddressDto : BaseEntity

   [Required]
   public string AddressLine1  get; set; 
   public string AddressLine2  get; set; 
   [Required]
   public string City  get; set; 
   public string State  get; set; 
   [Required]
   [DataType(DataType.PostalCode)]
   public string PostalCode  get; set; 

CustomerDto

public class CustomerDto : BaseEntity

   public string FirstName  get; set; 
   public string LastName  get; set; 
   public string Phone  get; set; 
   public string Email  get; set; 
   public string UserId  get; set; 
   public int AddressId  get; set; 
   public string UpdatedBy  get; set; 
   public DateTime? LastContact  get; set; 
   public virtual UserDto User  get; set; 
   public virtual AddressDto Address  get; set; 
   public virtual ICollection<InvoiceDto> Invoices  get; set; 

这是 AutoMapper 的配置

public class MappingConfiguration : Profile

   public MappingConfiguration()
   
      CreateMap<AddressDto, Address>().ReverseMap();
      CreateMap<CustomerDto, Customer>().ForMember(dst => dst.UserId,
       opt => opt.MapFrom(src => src.User.Id)).ForMember(dst => 
       dst.Address, 
       opt => opt.MapFrom(src => src.Address)).ReverseMap();
      CreateMap<CustomerCreateDto, Customer>().ReverseMap();
    

这里是Controller方法。我知道我可以通过使用addressDto 对象(如customer.Address = address)手动设置值来做到这一点,但我想看看是否有更好的方法

[HttpPost]
public async Task < IActionResult > Create([FromBody] CustomerCreateDto customerDto) 

   if (customerDto == null || !ModelState.IsValid) 
        return BadRequest(ModelState);
   

   var addressDto = new AddressDto 
        AddressLine1 = customerDto.AddressLine1,
        AddressLine2 = customerDto.AddressLine2,
        City = customerDto.City,
        State = customerDto.State,
        PostalCode = customerDto.PostalCode,
        CreatedDate = DateTime.Now,
    ;

    var user = await _userManager.FindByIdAsync(customerDto.UserId);
    var address = _mapper.Map<Address>(addressDto);
    var customer = _mapper.Map<Customer>(customerDto);
    
    customer.CreatedDate = DateTime.Now;

     var result = await _customerRepository.CreateAsync(customer);
     if (result) return Created("Created", new  customer );
     // if we got this far it means the request failed
     return new 
       StatusCodeResult(StatusCodes.Status500InternalServerError);

【问题讨论】:

AutoMapper 将您的 DTO 映射到实体中,但它不知道如何填充导航属性。所以你必须创建并保存地址(_addressRepo.CreateAsync(address))并分配这个对象customer.Address = address。您可以使用 AutoMapper 自动执行此操作,但代码有点讨厌 您可以添加导航。道具。进入 DTO。在映射器方面,您可以处理映射。 我没有看到CustomerCreateDto 的任何映射。同时,我看到CustomerDtoAddressDto 的映射未在此处显示。那么问题/问题到底是什么? 我已经添加了这些 【参考方案1】:

我正在寻找的功能是通过将 CustomerCreateDto 更改为:

public class CustomerCreateDto

   public string FirstName  get; set; 
   public string LastName  get; set; 
   public string Phone  get; set; 
   public string Email  get; set; 
   public string UserId  get; set; 
   public virtual AddressDto Address  get; set; 

映射配置:

public class MappingConfiguration : Profile

   public MappingConfiguration()
   
      CreateMap<AddressDto, Address>().ReverseMap();
      CreateMap<CustomerDto, Customer>().ForMember(dst => dst.UserId,
        opt => opt.MapFrom(src => src.User.Id));
      CreateMap<CustomerCreateDto, Customer>().ForMember(dst => dst.Address,
        opt => opt.MapFrom(src => src.Address)).ReverseMap().ReverseMap();
    

我的控制器:

[HttpPost]
public async Task<IActionResult> Create([FromBody] CustomerCreateDto customerDto) 

   if (customerDto == null || !ModelState.IsValid) 
        return BadRequest(ModelState);
   
   var customer = _mapper.Map<Customer>(customerDto);
    
   customer.CreatedDate = DateTime.Now;

   var result = await _customerRepository.CreateAsync(customer);
   if (result) return Created("Created", new  customer );

   return new StatusCodeResult(StatusCodes.Status500InternalServerError);

【讨论】:

以上是关于使用 AutoMapper 映射两个模型的主要内容,如果未能解决你的问题,请参考以下文章

[使用Automapper时,我是否也应该展平/映射视图模型的内部objetc?

AutoMapper 根据其他模型属性值映射所有属性

如何使用automapper映射与多个表的数据集

0.AutoMapper

如何使用AutoMapper创建复杂的映射?

使用链接地图/传递映射/链式地图的 AutoMapper 地图