2年目のエンジニアが覚えたAutoMapperの編集機能についてまとめてみた

#2年目エンジニアのメモ ~AutoMapperで値の転送を行うには~

##1. はじめに

先日、業務で`AutoMapper`について学習しました。
自分の理解を深めるためにメモを兼ねて記事を作成します。

この記事では`Fruit.cs`と`Apple.cs`という2つのクラスを作成し、前者から後者へ値を転送するやり方について説明していきます。
ここで扱う転送処理は次の通りです。

- 単純転送
- 固定値転送
- 編集を伴う転送

### 動作環境

この記事の内容は以下の環境で検証しました。

- VisualStudio2019<br>
- AutoMapper v.7.0.1<br>

##2. 作り方

FruitクラスからAppleクラスに値を転送する例を書いてみました。
以下のような仕様であると仮定します。

1. `Fruit`というクラスを作成し、プロパティにID、産地、賞味期限、ブランドコードの4項目を定義します。
1. `Apple`というクラスを作成し、ID、産地、賞味期限、ブランドコード、ブランド名、価格の6項目を定義します。
1. `Fruit`から`Apple`の転送仕様は次の通りとします。

| Fruit.cs         | Apple.cs          | 転送方法                        |
|:----------------:|:-----------------:|:------------------------------:|
| ID               | ID                | 同じ項目名の単純転送             |
| Origin           | ProductionArea    | 違う項目名の単純転送             |
| ExpirationDate   | ExpirationDate    | 3日前を計算して転送              |
| BrandCode        | BrandCode         | 同じ項目名の単純転送             |
| -                | BrandName         | BrandCodeを元に編集             |
| -                | Price             | 固定値転送                      |

先にソースコードをすべて示します。

```C#:Fruit.cs
using System;
public class Fruit
{
    public int ID { get; set; }
    public string Origin { get; set; }
    public DateTime ExpirationDate { get; set; }
    public int BrandCode { get; set; }
}
```

```C#:Apple.cs
using System;
public class Apple
{
    public int ID { get; set; }
    public string ProductionArea { get; set; }
    public DateTime ExpirationDate { get; set; }
    public int BrandCode { get; set; }
    public string BrandName { get; set; }
    public decimal Price { get; set; }
}
```

```C#:AppleMappingProfile.cs
using AutoMapper;
public class AppleMappingProfile : Profile
{
    public AppleMappingProfile() : base()
    {
        CreateMap<Fruit, Apple>()
        .ForMember(apple => apple.ProductionArea, option => option.MapFrom(fruit => fruit.Origin))
        .ForMember(apple => apple.ExpirationDate, option => option.MapFrom(fruit => (fruit.ExpirationDate).Add(-3)))
        .ForMember(apple => apple.BrandName, option => option.ResolveUsing<AppleBrandNameResolver>())
        .ForMember(apple => apple.Price, option => option.UseValue(120))
    }
}
```

```C#:AppleBrandNameResolver.cs
using System;
public class AppleBrandNameResolver
{
    public AppleBrandNameResolver : IValueResolver<Fruit, Apple, string>
    {
        public string Resolve(Fruit fruit, Apple apple, string destMember, ResolutionContext context)
        {
            if (fruit.BrandCode.Value == 111) return "ふじ";
            if (fruit.BrandCode.Value == 222) return "ジョナゴールド";
            return "その他の品種";
        }
    }
}
```

転送仕様を定義する`AppleMappingProfile.cs`というクラスを作成します。
`CreateMap<Fruit, Apple>()`で`Fruit`クラスから`Apple`クラスへの転送であることを宣言します。
`CreateMap<>()`の<>の中に編集元項目と編集先項目を入れてマッピングを行います。

### (1) 同じ項目名の単純転送

同じ項目名の場合は`.ForMember`を書く必要がなく、`CreateMap<>()`のみで転送が行われます。
`Apple.cs`の`ID`と`BrandCode`がこちらに当てはまります。

今回はそれぞれ`Fruit.cs`の`ID`と`BrandCode`を転送しています。

### (2) 違う項目名の単純転送 : MapFrom

`.ForMember`メソッドの引数に編集先項目(apple)を取り、`MapFrom`メソッドの引数に編集元項目(fruit)を取ります。
`Apple.cs`の`ProductionArea`がこちらに当てはまります。

今回は`Fruit.cs`の`Origin`を転送しています。

### (3) 3日前を計算して転送 : MapFrom

`.ForMember`メソッドの引数に編集先項目(apple)を取り、`MapFrom`メソッドの引数に編集元項目(fruit)を取ります。
`Apple.cs`の`ExpirationDate`がこちらに当てはまります。

`MapFrom`メソッドの => の先で編集元項目(fruit)から計算します。

今回は`Fruit.cs`の`ExpirationDate`から3日を引いた値を転送しています。

### (4) BrandCodeを元に編集 : ValueResolver

`.ForMember`メソッドの引数に編集先項目(apple)を取り、`ResolveUsing`メソッドの型に編集用の`ValueResolver`を取ります。
`Apple.cs`の`BrandName`がこちらに当てはまります。

`ResolveUsing`メソッドを使うためには`ValueResolver`の実装として`AppleBrandNameResolver`を用意します。
`AppleBrandNameResolver`は`IValueResolver`を実装する必要があり、1つ目の型に編集元項目(Fruit)、2つ目の型に編集先項目(Apple)、3つ目の型に設定する項目の型を設定します。

今回は`BrandCode`が「111」の場合に「ふじ」、「222」の場合に「ジョナゴールド」、その他の場合は「その他の品種」と返すようにif文で条件を設定しています。

### (5) 固定値転送 : UseValue

`.ForMember`メソッドの引数に編集先項目(fruit)を取り、`UseValue`メソッドの引数に固定値として設定する値を取ります。
`Apple.cs`の`Price`がこちらに当てはまります。

今回は固定値120を転送しています。

##3. 使い方

作成した`AppleMappingProfile.cs`を使うためには以下のようにして呼び出します。

~~~C#:FruitExtension.cs
var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<AppleMappingProfile>();
    });
var mapper = config.CreateMapper();
var apple = mapper.Map<Apple>(fruit);
~~~

##4. まとめ

以上が今回私が学習した`AutoMapper`の編集機能です。

最初は自分がQiitaに記事を投稿するとは思ってもみませんでしたが、現場リーダーの~~強要~~すすめで時間を取っていただき挑戦することができました。
記事の作成にあたり多くのことを教えていただき、本当にありがとうございます。
自分の理解を深めるためにメモを兼ねて書いた記事ですが、自分と同様プログラミングを始めたばかりの方や`AutoMapper`をこれから使ってみようという方に読んでいただけますと幸いです。