きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

AutoMapperを使ってオブジェクトを詰め替える

本日、ASP.NET MVC Do's and Dont's / Best Practices というまとめ記事が上がっていました*1

その中の一つ(少し古い記事ですが内容は大変に有用だと思います)の12 ASP.NET MVC Best Practicesを見ていたら、

DomainModelからViewModelに詰め替える時には退屈だしエラー積み込むかもしれんからバカ正直にコード書かずにAutoMapperとか使おうぜ(超訳)

とか書いてあるのを見つけました。

AutoMapper、そういえば以前どこかで割と普通なお方がお話されてた資料を見た記憶があるなぁ、と思いだしつつ*2、試してみることにしました。

AutoMapperとは?

AutoMapperとは、その名の通り(?)、オブジェクト同士を自動でマッピングしてくれるライブラリです。 以前はCodePlexで開発されていましたが、現在はGitHubがアクティブなリポジトリとなっているようです。

ざっくり概要を列挙するとこんなところでしょうか?

  • オブジェクトからオブジェクトにデータをコピーして詰め替える
  • コピーする際、プロパティ名の一致等の規約に沿って、自動でマップを行う
  • マッピングの規則はカスタマイズ可能
  • コレクションからコレクションへのマッピングも可能
  • NuGetで簡単に導入可能

さっそく、試してみましょう。 今回はシンプルに、コンソールアプリケーションのプロジェクトを使ってサンプルを作ってみます。

(以下のサンプルの完全なソリューション一色はgithubに置いてあります: kiyokura/AutoMapperSample · GitHub

 

 

AutoMapperの導入

AutoMapperの導入は、先に述べたとおりNuGetで一発で導入可能です。 パッケージ マネージャー コンソールから以下のようにインストールするか、GUIからAutoMapperで検索してインストールします。

PM> Install-Package AutoMapper

これだけで終了です。簡単ですね。

AutoMapperの基本

AutoMapperの基本的な使い方は次の2ステップのようです。

1. マップを作成

まず、Mapper.CreateMap<TSource, TDestination>()メソッドを使ってマップを作成する

2. マップを実行

Mapper.Map<TDestination>(object source)メソッドを使ってマップを実行

 

シンプルですね。 いくつか、実際のサンプルを動かしてみたいと思います。

 

 

サンプル1 : メンバーがまったく同じオブジェクトのマッピング

まずは一対一でオブジェクトをコピーするケースです

こんな二つのクラスがあるとして……

public class User
{
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
}

public class Member
{
  public int ID { get; set; }
  public string Name { get; set; }
  public int Age { get; set; }
}

基本に忠実に、こんな感じでサクッと。

// DBからとってきた(という体裁の)Memberクラスを、
// 全く同じメンバを持つ、User にマッピングする例

// Mapperを作成
Mapper.CreateMap<Member, User>();

// DBか何かからMember型でデータをとってきたと思ってください
Member member = new Member()
{
  ID = 15,
  Name = "Narami Kiyokura",
  Age = 20
};

//  Member型のmemberをUser型にマップ
User user = Mapper.Map<User>(member);


Console.WriteLine("ID:{0}", user.ID);
Console.WriteLine("Name:{0}", user.Name);
Console.WriteLine("Age:{0}", user.Age);

Console.ReadKey();

 

 

サンプル2 : コピー元とコピー先でメンバが異なる場合

とはいえ、コピー元と先でメンバが完全一致しないケースや、なんらかの加工が必要なケースもあると思います。以下程度の場合は、ごく簡単な加工追加処理だけで対応可能です。

こんな二つのクラスをマッピングしてみます。

public class Student
{
  public int ID { get; set; }
  public string Name { get; set; }
  public string Class { get; set; }
  public string Club { get; set; }
}


public class People
{
  public int PeopleID { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string Class { get; set; }
  public int PrefCode { get; set; }
}

お互いに存在しないメンバーについては、そのまま何もしなくてもエラーにならず、無視されます。 その他の場合は、マップ時にForMember()メソッドを使ってマップを定義してあげることができます。

// DBからとってきた(という体裁の)Peopleクラスを、
// 一部異なる名前のフィールドを持つStudent にマッピングする例

// Mapperを作成
// .ForMemberメソッドで移行先の何をセットするか指定する
// Student.ID に PeopleID をマップ
// Student.Name に People.FirstName とPeople.LastName を組み立てた値をマップ
// Student.Class にはClassはマップしない
// 
// お互いに存在しないものは何もしなくても無視する(People.PrefCodeやStudent.Club)
Mapper.CreateMap<People, Student>()
  .ForMember(d => d.ID, o => o.MapFrom(s => s.PeopleID))
  .ForMember(d => d.Name, o => o.MapFrom(s => s.FirstName + " " + s.LastName))
  .ForMember(d => d.Class, o => o.Ignore());

// DBか何かからPeople型でデータをとってきたと思ってください
People people = new People()
{
  PeopleID = 15,
  FirstName = "Narami",
  LastName = "Kiyokura",
  Class = "これはマップしない",
  PrefCode = 33
};

//  People型のpeopleをStudent型にマップ
Student student = Mapper.Map<Student>(people);


Console.WriteLine("ID:{0}", student.ID);
Console.WriteLine("Name:{0}", student.Name);
Console.WriteLine("Class:{0}", student.Class);
Console.WriteLine("Club:{0}", student.Club);

Console.ReadKey();

もっと複雑になる場合も対応可能ですが、今回は割愛します。 (公式ドキュメントに詳しくいろいろあります)

 

 

サンプル3 : コレクションからコレクションへのマッピング

最後に、コレクションからコレクションへのマップを試してみたいと思います。これも利用する頻度は高くなりそうです。

サンプル1で利用したMemberUserを利用します。

// DBからとってきた(という体裁の)Memberクラスのコレクションを、
// 全く同じメンバを持つ、Userのコレクション にマッピングする例

// Mapperを作成
// ここで指定するのはMember型とUser型
Mapper.CreateMap<Member, User>();

// DBか何かからMember型のコレクションでデータをとってきたと思ってください
List<Member> members = new List<Member>(){
    new Member()
    {
      ID = 15,
      Name = "Narami Kiyokura",
      Age = 20
    },
                        new Member()
    {
      ID = 17,
      Name = "Taro Yamada",
      Age = 25
    }
  };

//  Member型のmemberをUser型にマップ
List<User> users = Mapper.Map<List<User>>(members);

foreach (var user in users)
{
  Console.WriteLine("ID:{0}", user.ID);
  Console.WriteLine("Name:{0}", user.Name);
  Console.WriteLine("Age:{0}", user.Age);
}


Console.ReadKey();

ポイントは以下でしょうか - Mapの作成時はクラス(コレクションではない)を指定 - Mapper.Map<T>()の型引数にコレクション型を指定

これも結構シンプルにできますね。

 

 

まとめ

ということで、AutoMapperをざっくり触ってみました。超便利ですね。

他にも、Flatingといって、ネストされたオブジェクトに対して命名即に沿った形で自動的にフラットなオブジェクトにマッピングする機能も持っていたりします。

AutoMapperについて少し検索してみると、5年近く前にMVP for ASP.NET/IIS の青木さんが紹介記事『AutoMapperでオブジェクト間のデータコピーを行う』を執筆されておられたことを知りました。

青木さんの記事では、私が上記で試していないパターンやASP.NET MVCで実用的に使う場合のマッピング定義の扱い方などについても書かれておられます。AutoMapperに興味を持たれた方は是非、目を通されることをお勧めしたいと思います。

 

 

参考

*1:ちなみにこのサイトはマイクロソフトがホストしているキュレーションサイト『Curah!』(読み:キュラー)という、いわゆるまとめサイト的な奴 : http://curah.microsoft.com/

*2:割と普通氏のスライド資料はこちら:http://www.slideshare.net/normalian/clrh-69