オープンソースのライブラリ「AutoMapper」を使うと、異なるクラス間のデータコピーを自動化できる。「基本編」ではプロパティ名が同じ場合を紹介したが、プロパティ名が異なる場合はどうなのだろうか? 独自のマッピングを定義することで、その場合もデータコピーを自動化できる。さらには、データをコピーする際に型変換などの処理を実行させることもできるのだ。本稿では、そのようなAutoMapperのマッピング機能の使い方を紹介する。
なお、本稿執筆時点でAutoMapperがサポートしているのは.NET Framework 4以降であるが、NuGetから導入するため、本稿ではVisual Studio 2012を使って説明する。また、本稿のサンプルは「MSDN Code Recipe:.NET Tips #1103」からダウンロードできる。
「.NET TIPS:AutoMapperを使ってオブジェクト間のデータコピーを自動化するには?(基本編)」をご覧いただきたい。
ここでは例題として、催事の情報を格納する「EventData」クラスと「EventDataForDisplay」クラスという二つのクラスを考えてみよう。この二つのクラスのオブジェクト間で、次の図のようなデータコピーを行いたいものとする。
「EventData」クラスはロジックで使うオブジェクトだとする。ロジックで使うオブジェクトは、シンプルなものにしたい。例えば次のコードのようになる。
using System;
using System.Collections.Generic;
namespace dotNetTips1103VS2012
{
public class EventData
{
public string Title { get; set; }
public DateTime DateTime { get; set; }
public string GetFormattedDateTime()
{
return DateTime.ToString("yyyy/MM/dd HH:mm");
}
public static IList<EventData> GetData()
Public Class EventData
Public Property Title As String
Public Shared Function GetData() As IList(Of EventData)
「Title」と「DateTime」という二つのプロパティおよび書式化された日時を返す「GetFormattedDateTime」メソッドを持つシンプルなクラスである。
また、何らかの方法でデータを取得してくる「GetData」メソッドがある(このクラスに配置することには異論があるかもしれないが、サンプルということでご容赦願いたい)。
なお、このVBのコードでは、Visual Basic 2005からの機能である
ジェネリック型や、Visual Basic 2008から利用できるようになった
オブジェクト初期化子、そしてVisual Basic 2010から利用できるようになった
自動実装プロパティを使用している。
次に、「EventDataForDisplay」クラスは、WPFの画面にバインドして表示するためのものだとする。画面には、次に挙げるプロパティを表示したいものとする。
EventTitle(イベントのタイトル)/EventDate(イベントの日付)/EventTime(イベントの開始時刻)/FormattedDateTime(イベント開催日時)
また、データの変更を画面に反映させたいので、INotifyPropertyChangedインターフェース(System.ComponentModel名前空間)も実装する。すると、次のコードのようにちょっと複雑なクラスになる。
using System.Collections.Generic;
namespace dotNetTips1103VS2012
{
public class EventDataForDisplay : System.ComponentModel.INotifyPropertyChanged
{
// 公開するイベントハンドラー(INotifyPropertyChangedインターフェースの実装)
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new System.ComponentModel.PropertyChangedEventArgs(info));
}
// 表示用のイベントタイトル(元データではTitleプロパティ)
// 表示用の日付(元データはDateTime型だが、これはString型)
// 表示用の時刻(元データはDateTime型だが、これはString型)
// 表示用の日時(元データにはGetFormattedDateTimeという名前のメソッドで実装されている)
Public Class EventDataForDisplay
Implements ComponentModel.INotifyPropertyChanged
' 公開するイベントハンドラー(INotifyPropertyChangedインターフェースの実装)
' 表示用のイベントタイトル(元データではTitleプロパティ)
' 表示用の日付(元データはDateTime型だが、これはString型)
' 表示用の時刻(元データはDateTime型だが、これはString型)
' 表示用の日時(元データにはGetFormattedDateTimeという名前のメソッドで実装されている)
ロジックで使う「EventDataForDisplay」クラスの例(上:C#、下:VB)
先ほどのロジックで使う「EventData」クラスに比べて、かなり長いコードになっている。データの変更を画面に反映させるためにINotifyPropertyChangedインターフェースを実装したためだ。
このような複雑なクラスはロジックで使いたくない。また、ロジックと関係のないSystem.ComponentModel名前空間がロジックに入ってくるのも避けたいものだ。そのため、このように「EventData」と「EventDataForDisplay」の二つのクラスに分けて設計を進めることになる。
また、画面には「EventDataForDisplay」オブジェクトのリストを表示したいとする。デザイン画面での利便性を考えると、「EventDataForDisplay」オブジェクトのコレクションを持つ「EventsList」クラスも必要になる(次のコード)。
public class EventsList
{
private System.Collections.ObjectModel.ObservableCollection<EventDataForDisplay>
_events = new System.Collections.ObjectModel.ObservableCollection<EventDataForDisplay>
(new List<EventDataForDisplay>());
public System.Collections.ObjectModel.ObservableCollection<EventDataForDisplay>
Events { get { return _events; } }
public EventsList()
Public Class EventsList
Private _events _
As System.Collections.ObjectModel.ObservableCollection(Of EventDataForDisplay) _
= New System.Collections.ObjectModel.ObservableCollection(Of EventDataForDisplay) _
(New List(Of EventDataForDisplay)())
Public ReadOnly Property Events _
As System.Collections.ObjectModel.ObservableCollection(Of EventDataForDisplay)
Get
Return _events
End Get
End Property
Public Sub New()
EventDataForDisplayオブジェクトを格納するObservableCollectionオブジェクト(System.Collections.ObjectModel名前空間)を、「Events」プロパティとして公開している。
WPFのXAMLコードの側でEventDataForDisplayオブジェクトのコレクションを生成してバインドするには、このようなクラスを用意しておくとよい。省略した部分でダミーデータを設定すれば、デザイン画面でもデータが表示されるようになる。
ちなみに、コードビハインドでObservableCollectionオブジェクトを生成して画面のコントロールにバインドするのであれば、このクラスは不要だ。しかしそうすると、デザイン画面ではデータが表示されない。
なお、このVBのコードでは、Visual Basic 2005からの機能である
ジェネリック型を使用している。
この「EventsList」クラスをWPFのListViewコントロール(System.Windows.Controls名前空間)にバインドして表示させた例を、次の画像に示す。
AutoMapperのForMember拡張メソッドを使えばよい。
「基本編」で説明したようにMapperクラス(AutoMapper名前空間)のCreateMapメソッドを呼び出したら、それに続けてForMember拡張メソッドを呼び出す。ForMember拡張メソッドの引数は二つで、次のようだ。
第2引数のラムダ式に使うMapFrom拡張メソッドは、一つのラムダ式を引数に取る。コピー元からコピーしたいデータを取り出すラムダ式を与える。
このように説明するとなんだか複雑そうだが、次からの具体例を見てほしい。
例えば、コピー元の「Title」プロパティの値をそのままコピー先の「EventTitle」プロパティにコピーする指定は、次のようなコードになる。
AutoMapper.Mapper.CreateMap<EventData, EventDataForDisplay>()
.ForMember("EventTitle", opt => opt.MapFrom(src => src.Title));
AutoMapper.Mapper.CreateMap(Of EventData, EventDataForDisplay)() _
.ForMember("EventTitle", Sub(opt) opt.MapFrom(Function(src) src.Title))
上のコードで、MapFrom拡張メソッド内にコピー元からデータを取り出すラムダ式を書いた。このラムダ式の返値がコピーされるのであるから、ここに型変換などの処理を書けばよい。例えば、コピー元のDateTime型を日付または時刻の文字列型に変換してコピーするには、次のようなコードになる。
AutoMapper.Mapper.CreateMap<EventData, EventDataForDisplay>()
.ForMember("EventTitle", opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.EventDate,
opt => opt.MapFrom(src => src.DateTime.ToString("M月d日")))
.ForMember(dest => dest.EventTime,
opt => opt.MapFrom(src => src.DateTime.ToString("H時m分")));
AutoMapper.Mapper.CreateMap(Of EventData, EventDataForDisplay)() _
.ForMember("EventTitle", Sub(opt) opt.MapFrom(Function(src) src.Title)) _
.ForMember(Function(dest) dest.EventDate,
Sub(opt) opt.MapFrom(Function(src) src.DateTime.ToString("M月d日"))) _
.ForMember(Function(dest) dest.EventTime,
Sub(opt) opt.MapFrom(Function(src) src.DateTime.ToString("H時m分")))
1行目/2行目は前のコードと同じ。
太字にした3行目/4行目が、コピー元のデータを加工してからコピーする独自マッピングの定義である。ForMember拡張メソッドの第1引数にはコピー先のプロパティを指定する(ここではラムダ式を使った)。第2引数のMapFrom拡張メソッド内に与えるラムダ式で、コピー元のデータを加工して返すようにしている(ここではDateTime型のToStringメソッドを呼び出している)。
なお、このVBのコードでは、Visual Basic 2008から利用できるようになった
ラムダ式と、Visual Basic 2010から利用できるようになった
暗黙の行連結を使用している。
もうお分かりだと思うが、MapFrom拡張メソッドに与えるラムダ式でコピー元のメソッドを呼び出せばよい。例えば、次のコードのようにだ。
.ForMember(dest => dest.FormattedDateTime,
opt => opt.MapFrom(src => src.GetFormattedDateTime()));
.ForMember(Function(dest) dest.FormattedDateTime,
Sub(opt) opt.MapFrom(Function(src) src.GetFormattedDateTime()))
上のように書いてもよいのだが、実はAutoMapperは、コピー先と同じ名前のプロパティがコピー元にないときに、プロパティ名の先頭に「Get」を付けた名前のメソッドを自動的に探してくれるのである。すなわち、今回の例題では上のコードを記述しなくてもよいのだ。
以上をまとめて、例題の図に示したコピーを行うコードは次のようになる。
// ロジックを呼び出してデータを取得する
IList<EventData> data = EventData.GetData();
// 表示用のコレクションを用意する
// AutoMapperを使ってデータを表示用のオブジェクトにコピーする
// 表示用のコレクションをデータコンテキストにセット
' ロジックを呼び出してデータを取得する
Dim data As IList(Of EventData) = EventData.GetData()
' 表示用のコレクションを用意する
' AutoMapperを使ってデータを表示用のオブジェクトにコピーする
' 表示用のコレクションをデータコンテキストにセット
Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.