# 八、对象映射指南

## 传统模式（不推荐）

假设有两个类型：`Student` 和 `StudentDto`，现在需要将 `Student` 对象赋值给 `StudentDto`

`Student`

```csharp
public class Student
{
    public string FirstName {get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string Address { get; set; }
    public string Gender {get; set; }
}
```

`StudentDto`

```csharp
public class StudentDto
{
    public string FullName {get; set; }
    public int Age { get; set; }
    public string Gender {get; set; }
}
```

传统赋值模式代码如下：

```csharp
var student = new Student() 
{
    FirstName = "Monk",
    LastName = "Soul",
    Age = 27,
    Address = "广东省珠海市香洲区吉大路",
    Gender = "男"
};

// 赋值

var studentDto = new StudentDto()
{
    FullName = student.FirstName + student.LastName,
    Age = student.Age,
    Gender = student.Gender
}
```

通过上面的代码，可能一部分读者觉的这是一件很正常的代码，没有什么不妥。

确实，这样的代码咋看没什么不妥，但是**有几个漏洞**：

1. 如果字段繁多，这赋值操作相当耗时间且容易出错
2. 如果多次需要将 Student映射到 StudentDto中，则这样的代码散落在很大地方，给维护带来了极大的困扰
3. 这样的赋值操作实际上污染了业务逻辑代码，不利于后续的统一管理
4. 这样的赋值操作实在是多，无意增加了项目包的大小，开发效率也会低下

所以，我们迫切需要一种更加灵活、简易的对象赋值（映射）方式。

## 对象映射组件（推荐）

正是由于传统组件带来的漏洞及效率低下问题，所以就有第三方开发者提供了解决方案，在众多的解决方案中，无外乎 [AutoMapper](https://github.com/AutoMapper/AutoMapper) 和 [Mapster](https://github.com/MapsterMapper/Mapster) 最为出色。

由于[ Mapster](https://github.com/MapsterMapper/Mapster) 简单易用，而且性能极高，所以 Hoa Framework 将 Mapster 作为默认的映射组件。

## Mapster 使用

### 简单使用

将源对象映射到目标对象，并**创建新的目标对象**

```csharp
var destObject = sourceObject.Adapt<Destination>();
```

将源对隐射到**已存在的目标对象**

```csharp
var destObject = new DestObject(){...};
sourceObject.Adapt(destObject );
```

### 自定义映射规则

通常，我们的属性并非总是一一对应映射，我们可能会进行一些操作或重写，比如：`StudentDto.FullName` 由 `Student.FirstName` + `Student.LastName` 组成。

自定义映射支持多种方式，在 Hoa Framework 中，建议程序员将 自定义映射规则配置在 `Hoa.Application.ManualMapper.cs`中，方便集中管理。

```csharp
using Mapster;

namespace Hoa.Application
{
    public class ManualMapper : IRegister
    {
        public void Register(TypeAdapterConfig config)
        {
            config.ForType<SourceObject, DestinationObject>()
                .Map(dest => dest.FullName,
                     src => string.Format("{0} {1}", src.FirstName, src.LastName));

            config.ForType<DestinationObject, SourceObject>()
                .Map(dest => dest.Gender,
                     src => src.GenderString);
                     
            config.ForType<SourceObject, DestinationObject>()
                .Map(dest => dest.FullName, src => "Sig. " + src.FullName, srcCond => srcCond.Country == "Italy")
                .Map(dest => dest.FullName, src => "Sr. " + src.FullName, srcCond => srcCond.Country == "Spain")
                .Map(dest => dest.FullName, src => "Mr. " + src.FullName);
        }
    }
}
```

更多自定义映射规则[可查看官方文档](https://github.com/MapsterMapper/Mapster/wiki/Custom-mapping)。

### 特殊映射

Hoa Framework 内置了将类似 `GROUP_ID`->`GroupId`和`GroupId`->`GROUP_ID`两种映射方式，这是 [Mapster](https://github.com/MapsterMapper/Mapster) 默认不具备的功能。

配置`Hoa.Application.ManualMapper.cs`如下：

```csharp
using Mapster;
using System.Text;

namespace Hoa.Application.Authorization.Dtos
{
    public class Mapper : IRegister
    {
        public void Register(TypeAdapterConfig config)
        {
            config.ForType<SignInInput, SignInInput2>()
                .ConvertSourceMemberUnderlineSplitNameToCamelCase();    // 支持：GROUP_ID -> GroupId 方式

            config.ForType<SignInInput2, SignInInput>()
                .ConvertSourceMemberCamelCaseNameToUnderlineSplit();    // 支持：GroupId -> GROUP_ID 方式
        }
    }
}
```

### 数据库实体映射说明

将 EF Core 查询的结果集映射到 Dto 中。

#### :flag\_black: **传统模式（不推荐）：**

```csharp
using (MyDbContext context = new MyDbContext())
{
    // 不使用 Mapster 时候
    var destinations = context.Sources.Select(c => new Destination {
        Id = p.Id,
        Name = p.Name,
        Surname = p.Surname,
        ....
    })
    .ToList();
}
```

#### :flag\_black: **使用** [Mapster](https://github.com/MapsterMapper/Mapster) **之后（推荐）：**

```csharp
using (MyDbContext context = new MyDbContext())
{
    // 使用 Mapster 后
    var destinations = context.Sources.ProjectToType<Destination>().ToList();
}
```

### 将 `Dto` 映射到已存在的 `Entity`

```csharp
var entity = _testRepository.GetFirstOrDefault(predicate: u =>u.Id == 1);
var dto = new Dto() { Name = "Monk", Age = 27};
dto.Adapt(entity);  // 这时，Entity会自动更新 Name和Age属性，其他的保持不变
```

**注意** 如果 `Dto` 中某些值为 `Null`，默认也会将 `Null`值更新到 `Entity`，**这可能不是我们想要的！！！**&#x6240;以，我们需要手动配置一下，`Null` 值不需要映射。

```csharp
config.ForType<Dto, Entity>()
    .IgnoreNullValues(true);
```

更多  [Mapster](https://github.com/MapsterMapper/Mapster)  [可查看官方文档](https://github.com/MapsterMapper/Mapster/wiki)。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://monksoul.gitbook.io/hoa/duixiangyingshe.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
