AutoMapperを使って、名前の異なるプロパティ/型が異なるプロパティなどの間でのデータコピーを自動化する方法を解説する。
対象:.NET 4以降
オープンソースのライブラリ「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()
{
var list = new List<EventData>();
// ダミーのデータ(実際にはデータベースなどから取得してくると思ってほしい)
list.Add(new EventData() { Title="とっておきイベント その1",
DateTime=new DateTime(2015,3,24,15,0,0), });
list.Add(new EventData() { Title = "とっておきイベント その2",
DateTime = new DateTime(2015, 4, 30, 0, 30, 0), });
list.Add(new EventData() { Title = "とっておきイベント その3",
DateTime = new DateTime(2015, 5, 26, 10, 0, 0), });
return list;
}
}
}
Public Class EventData
Public Property Title As String
Public Property DateTime As DateTime
Public Function GetFormattedDateTime() As String
Return DateTime.ToString("yyyy/MM/dd HH:mm")
End Function
Public Shared Function GetData() As IList(Of EventData)
Dim list = New List(Of EventData)()
' ダミーのデータ(実際にはデータベースなどから取得してくると思ってほしい)
list.Add(New EventData() With {.Title = "とっておきイベント その1",
.DateTime = New DateTime(2015, 3, 24, 15, 0, 0)})
list.Add(New EventData() With {.Title = "とっておきイベント その2",
.DateTime = New DateTime(2015, 4, 30, 0, 30, 0)})
list.Add(New EventData() With {.Title = "とっておきイベント その3",
.DateTime = New DateTime(2015, 5, 26, 10, 0, 0)})
Return list
End Function
End Class
次に、「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プロパティ)
// 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
private string _eventTitle;
public string EventTitle
{
get { return _eventTitle; }
set
{
if (string.Equals(_eventTitle, value))
return;
_eventTitle = value;
NotifyPropertyChanged("EventTitle");
}
}
// 表示用の日付(元データはDateTime型だが、これはString型)
// 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
private string _eventDate;
public string EventDate
{
get { return _eventDate; }
set
{
if (string.Equals(_eventDate, value))
return;
_eventDate = value;
NotifyPropertyChanged("EventDate");
}
}
// 表示用の時刻(元データはDateTime型だが、これはString型)
// 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
private string _eventTime;
public string EventTime
{
get { return _eventTime; }
set
{
if (string.Equals(_eventTime, value))
return;
_eventTime = value;
NotifyPropertyChanged("EventTime");
}
}
// 表示用の日時(元データにはGetFormattedDateTimeという名前のメソッドで実装されている)
// 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
private string _formattedDateTime;
public string FormattedDateTime
{
get { return _formattedDateTime; }
set
{
if (string.Equals(_formattedDateTime, value))
return;
_formattedDateTime = value;
NotifyPropertyChanged("FormattedDateTime");
}
}
}
}
Public Class EventDataForDisplay
Implements ComponentModel.INotifyPropertyChanged
' 公開するイベントハンドラー(INotifyPropertyChangedインターフェースの実装)
Public Event PropertyChanged(sender As Object, e As ComponentModel.PropertyChangedEventArgs) _
Implements ComponentModel.INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(info As String)
RaiseEvent PropertyChanged(Me, _
New System.ComponentModel.PropertyChangedEventArgs(info))
End Sub
' 表示用のイベントタイトル(元データではTitleプロパティ)
' 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
Private _eventTitle As String
Public Property EventTitle As String
Get
Return _eventTitle
End Get
Set(value As String)
If (String.Equals(_eventTitle, value)) Then
Return
End If
_eventTitle = value
NotifyPropertyChanged("EventTitle")
End Set
End Property
' 表示用の日付(元データはDateTime型だが、これはString型)
' 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
Private _eventDate As String
Public Property EventDate As String
Get
Return _eventDate
End Get
Set(value As String)
If (String.Equals(_eventDate, value)) Then
Return
End If
_eventDate = value
NotifyPropertyChanged("EventDate")
End Set
End Property
' 表示用の時刻(元データはDateTime型だが、これはString型)
' 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
Private _eventTime As String
Public Property EventTime As String
Get
Return _eventTime
End Get
Set(value As String)
If (String.Equals(_eventTime, value)) Then
Return
End If
_eventTime = value
NotifyPropertyChanged("EventTime")
End Set
End Property
' 表示用の日時(元データにはGetFormattedDateTimeという名前のメソッドで実装されている)
' 値に変化があったときにはPropertyChangedイベントハンドラーを発火させる
Private _formattedDateTime As String
Public Property FormattedDateTime As String
Get
Return _formattedDateTime
End Get
Set(value As String)
If (String.Equals(_formattedDateTime, value)) Then
Return
End If
_formattedDateTime = value
NotifyPropertyChanged("FormattedDateTime")
End Set
End Property
End Class
また、画面には「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()
' デザイン時に表示するダミーデータを設定する
' ……省略……
End Sub
End Class
この「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分")))
もうお分かりだと思うが、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();
// 表示用のコレクションを用意する
var eventsList = new EventsList();
// AutoMapperを使ってデータを表示用のオブジェクトにコピーする
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分")));
foreach (EventData p in data)
eventsList.Events.Add(
AutoMapper.Mapper.Map<EventDataForDisplay>(p)
);
// 表示用のコレクションをデータコンテキストにセット
this.DataContext = eventsList;
' ロジックを呼び出してデータを取得する
Dim data As IList(Of EventData) = EventData.GetData()
' 表示用のコレクションを用意する
Dim eventsList = New EventsList()
' AutoMapperを使ってデータを表示用のオブジェクトにコピーする
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分")))
For Each p As EventData In data
eventsList.Events.Add(
AutoMapper.Mapper.Map(Of EventDataForDisplay)(p)
)
Next
' 表示用のコレクションをデータコンテキストにセット
Me.DataContext = eventsList
カテゴリ:オープンソース・ライブラリ 処理対象:データ型
カテゴリ:C# 処理対象:データ型
カテゴリ:Visual Basic 処理対象:データ型
関連TIPS:AutoMapperを使ってオブジェクト間のデータコピーを自動化するには?(基本編)
Copyright© Digital Advantage Corp. All Rights Reserved.