AutoMapperを使用したオブジェクトのマッピング
先日、ある方からAutoMapperというものを教えていただきました。
どうやらオブジェクトのマッピングが簡単に実現できるとのことなので早速調べた所、結構便利だったのでメモ
1.AutoMapperとは?
AutoMapperは、オブジェクト同士を自動的にマッピングしてくれるライブラリです。
このマッピングは定義した名称によって紐づくような仕組みになっているのですが、自分でマッピングするデータを変えることもできます。
ざっくりまとめるとこんな感じでしょうか。
2.導入方法
Nugetもしくは、拡張機能と更新プログラムからインストールしてください。
以下の公式リファレンスにはサンプルなども入ったソースプログラムがダウンロードできます。
3.基本的な使い方
早速使っていきます。
まず、マッピングの基本的な流れは次のようになります。
基本はこの流れで、2→3はほとんど変わることはないでしょう。
サンプル1.デフォルトのマッピング
まずは、何も指定しない状態のマッピングを実施します。
// マッピングの初期設定 // デフォルトマッピング Mapper.CreateMap<Source, Destination>(); var dt = DateTime.Now; // 元となるデータを用意 var src = new Source() { Number = dt.Millisecond, Text = dt.ToString("yyyyMMddHHmmssfff"), DayOfWeek = dt.DayOfWeek, }; Console.WriteLine(src.ToString()); // マッピング実施 var dst = Mapper.Map<Destination>(src); // この書き方でもおk // var dst = new Destination(); // Mapper.Map(src, dst); Console.WriteLine(dst.ToString());
<実行結果>
AutoMapperSample.Source
Number : 133
Text : 20150926165134133
DayOfWeek : Saturday
AutoMapperSample.Destination
Number : 133
Text : 20150926165134133
DayOfWeek : Saturday
ちゃんとマッピングされていますね。
同名のデータを持つオブジェクト同士であれば、このようにマッピング定義するだけで問題ありません。
また、コレクションをマッピングする場合も上記のマッピング定義を変えることなくマッピングが可能です。
要はマッピングする単一のオブジェクトのマッピング定義さえできていればコレクションだろうと配列だろうとマッピングは可能です。
サンプル2.マッピング対象から外す
オブジェクト間でどうしてもマッピングしたくないデータが有る場合は、以下のようにマッピング定義を変更します。
// Numberだけマッピング対象外とする。 Mapper.CreateMap<Source, Destination>() .ForMember(d => d.Number, opt => opt.Ignore());
<実行結果>
AutoMapperSample.Source
Number : 10
Text : 20150926171514010
DayOfWeek : Saturday
AutoMapperSample.Destination
Number : 0
Text : 20150926171514010
DayOfWeek : Saturday
Numberだけマッピング対象外となりました。
.ForMemberメソッドから対象外としたいプロパティに対して、opt.Ignore()と定義することで簡単に実現できました。
サンプル3.異なる名称のデータをマッピング
異なるデータ名のマッピングも需要があると思います。
その場合は、以下のように定義します。
// SourceにRemarksを、DestinationにRemarksDummyを追加した。 Mapper.CreateMap<Source, Destination>() .ForMember(d => d.RemarksDummy, opt => opt.MapFrom(s => s.Remarks));
<実行結果>
AutoMapperSample.Source
Number : 654
Text : 20150926172719654
DayOfWeek : Saturday
Remarks : Source Remarks
AutoMapperSample.Destination
Number : 654
Text : 20150926172719654
DayOfWeek : Saturday
RemarksDummy : Source Remarks
異なる名称のデータでもopt.MapFrom()メソッドを使用してマッピングできます。
サンプル4.異なる型のマッピング
これまでは同じ型のデータをマッピングしてきましたが、異なる型をマッピングする場合は以下のようになります。
// Destination.RemarksDummy にSource.DayOfWeek の文字列をマッピング Mapper.CreateMap<Source, Destination>() .ForMember(d => d.RemarksDummy, opt => opt.MapFrom(s => s.DayOfWeek.ToString()));
<実行結果>
AutoMapperSample.Source
Number : 379
Text : 20150926173621379
DayOfWeek : Saturday
Remarks : Source Remarks
AutoMapperSample.Destination
Number : 379
Text : 20150926173621379
DayOfWeek : Saturday
RemarksDummy : Saturday
先程と同様にMapFromメソッドで変換した結果を定義すればマッピングすることが可能です。
お気づきかもしれませんが、MapFromメソッドを使用すれば、複数のデータを組み合わせてマッピングすることも可能です。(以下参照)
Mapper.CreateMap<Source, Destination>() .ForMember(d => d.RemarksDummy, opt => opt.MapFrom(s => s.DayOfWeek.ToString() + s.Number.ToString()));
<実行結果>
AutoMapperSample.Source
Number : 647
Text : 20150926173936647
DayOfWeek : Saturday
Remarks : Source Remarks
AutoMapperSample.Destination
Number : 647
Text : 20150926173936647
DayOfWeek : Saturday
RemarksDummy : Saturday647
列挙型から数値へのマッピングなんかも同様に実現できます。
サンプル5.相互マッピング
これまでは、SourceからDestinationへのマッピングのみ見てきました。
DestinationからSourceへのマッピングを実現する場合は、次のように定義します。
// どちらからもマッピングできるように設定 // Source.Remarks ⇒ Destination.RemarksDummy // Destination.DayOfWeek ⇒ Source.Remarks // をマッピング Mapper.CreateMap<Source, Destination>() .ForMember(d => d.RemarksDummy, opt => opt.MapFrom(s => s.Remarks)) .ReverseMap() .ForMember(s => s.Remarks, opt => opt.MapFrom(d => d.DayOfWeek.ToString())); var dt = DateTime.Now; // 元となるデータを用意 var src = new Source() { Number = dt.Millisecond, Text = dt.ToString("yyyyMMddHHmmssfff"), DayOfWeek = dt.DayOfWeek, Remarks = "Source Remarks", }; Console.WriteLine(src.ToString()); // マッピング実施 var dst = Mapper.Map<Destination>(src); Console.WriteLine(dst.ToString()); dst.Text = "Changed dst."; // 逆方向へマッピング var newSrc = Mapper.Map<Source>(dst); Console.WriteLine(newSrc.ToString());
<実行結果>
AutoMapperSample.Source
Number : 941
Text : 20150926175837941
DayOfWeek : Saturday
Remarks : Source Remarks
AutoMapperSample.Destination
Number : 941
Text : 20150926175837941
DayOfWeek : Saturday
RemarksDummy : Source Remarks
AutoMapperSample.Source
Number : 941
Text : Changed dst.
DayOfWeek : Saturday
Remarks : Saturday
ReverseMap()を呼び出すことで逆方向へのマッピングも可能になります。
ReverseMap()を呼び出す前後でマッピング定義を設定していますが、これは1方向分の定義しかなされていません。
なので、きちんとどちらからマッピングしても整合性の合うように定義する必要があります。
サンプル6.マッピングのプロファイル
これまでは、1つのマッピング定義のみ見てきましたが、おそらく開発で組み込む場合は複数のマッピング定義を作成することになると思います。
そうなると、マッピング定義のみ行う管理機能が欲しくなってくると思いますが、AutoMapperにはそんな需要にも応えてくれます。
<MappingProfile.cs>
/// <summary> /// 独自マッピングのプロファイル設定 /// </summary> public class MappingProfile : Profile { /// <summary> /// マッピングの設定 /// </summary> /// <remarks> /// Configure()メソッドをオーバライドしてマッピングを設定していきます。 /// </remarks> protected override void Configure() { // ここにマッピング定義を書いていく。 Mapper.CreateMap<Source, Destination>() .ForMember(d => d.RemarksDummy, opt => opt.MapFrom(s => s.Remarks)) .ReverseMap() .ForMember(s => s.Remarks, opt => opt.MapFrom(d => d.RemarksDummy)); } }
<MainWindow.xaml.cs>
try { // マッピング定義の初期化 Mapper.Initialize(config => { // 複数のプロファイルがあれば、AddProfileで追加していく。 config.AddProfile<MappingProfile>(); // ここでは、マッピング名称ルールのカスタマイズ設定なども実施できる。 }); // マッピング設定を検証する。 // マッピングするデータの過不足があれば、ここで例外が発生する。 // ただし、キャストの失敗などは検知できないので注意すること。 Mapper.AssertConfigurationIsValid(); } catch (Exception ex) { // マッピング失敗時のエラー処理を実施 Console.WriteLine(ex.Message); }
マッピングの定義はAutoMapper.Profileを継承したクラスに書いていき、Maper.Initializeで初期化処理を実施する。
初期化処理内部のAddProfileにマッピング定義したProfileクラスを設定すると自動的にマッピング定義が適応されます。
また、
Mapper.AssertConfigurationIsValid();
とすることで、マッピングの過不足があるかどうかを検証してくれます。
(検証エラーの場合、例外が発行されます。)
ここで検証するのは、あくまでもマッピングの過不足であって、キャスト可能かどうかは判定できません。
まとめ
このようにいろいろな需要に対応してくれているAutoMapperは非常に強力です。
MVVMでアプリケーションを作成する場合、ModelからViewModelへデータを移し替えたり、DBからテーブルを結合したModelを作ってデータを入れる場合なんかは重宝しそうですね。
そして何よりデータの移し替えが楽ちんすぎます。
複数のデータ移し替え作業が発生した場合は使用を検討してみてはいかがでしょうか。
参考ページ
AutoMapperでオブジェクト間のデータコピーを行う (1) AutoMapperとは | マイナビニュース
AutoMapperを使ってオブジェクトを詰め替える - きよくらの備忘録
AutoMapperの基本的な使い方メモ - いろいろ備忘録日記