WebアプリケーションやMVVMに則ったクライアントアプリケーションを作っていると、ModelとViewModelの相互変換がよく行われます。設計におけるレイヤーの違いを埋めるこの型変換ですが、基本的に同じような名前/型のプロパティが並ぶため、変換コードの実装が大変に煩わしいです。この処理を (ある程度) 自動化したい、そう思うのはプログラマとしては半ば当然でしょう。
上記のような型変換を補助するライブラリとしてAutoMapperがあります。AutoMapperの基本的な使い方は以下をご参照ください。
AutoMapperを使ってオブジェクトを詰め替える
AutoMapperの基本とちょっとしたTips
AutoMapperでオブジェクト間のデータコピーを行う
名前の変換規則
AutoMapperは基本的には名称ベースでプロパティの詰め替えを行ってくれます。その変換の挙動がどうなっているのか、簡単に調べてみます。まず、以下のようないろいろなケースのプロパティを持つクラスを用意しておきます。
class PascalCase { public DateTime BirthDay { get; set; } } class camelCase { public DateTime birthDay { get; set; } } class UPPER_SNAKECASE{ public DateTime BIRTH_DAY { get; set; } } class lower_snakecase{ public DateTime birth_day { get; set; } }
これに対してAutoMapperで変換を行うと、以下のようになります。
//--- 変換方法をAutoMapperに登録 Mapper.Initialize(config => { config.CreateMap<PascalCase, camelCase>(); config.CreateMap<PascalCase, UPPER_SNAKECASE>(); config.CreateMap<PascalCase, lower_snakecase>(); }); //--- 変換元のインスタンス var source = new PascalCase { BirthDay = new DateTime(1984, 11, 6, 1, 23, 45), }; //--- AutoMapperで変換 var pascal = Mapper.Map<PascalCase>(source); var camel = Mapper.Map<camelCase>(source); var upper = Mapper.Map<UPPER_SNAKECASE>(source); var lower = Mapper.Map<lower_snakecase>(source); //--- 変換結果を確認 Console.WriteLine("PascalCase : {0}", pascal.BirthDay); Console.WriteLine("camelCase : {0}", camel.birthDay); Console.WriteLine("UPPER_SNAKECASE : {0}", upper.BIRTH_DAY); Console.WriteLine("lower_snakecase : {0}", lower.birth_day); /* PascalCase : 1984/11/06 1:23:45 camelCase : 1984/11/06 1:23:45 UPPER_SNAKECASE : 0001/01/01 0:00:00 lower_snakecase : 1984/11/06 1:23:45 */
この結果を見ると、UPPER_SNAKECASE以外へのケースへの変換が自動で行われるようです。UPPER_SNAKECASEなプロパティを持つクラスを生成することはまず滅多にないですが、Oracleデータベースのテーブル構造をそのままマッピングクラスとして再現したりする場合には無きにしも非ずかと思います。このような場合はForMemberメソッドを並べてプロパティ間の差異を埋めることもできますが、それではすべてのプロパティの変換を記述する必要が出てくるためAutoMapperを利用する旨みが全くありません。しかし楽は常に追求したいですし、AutoMapperの力を活用せずにココで「UPPER_SNAKECASE残念乙!m9」と終わるわけには行きません。
大文字スネークケースとの変換を簡略化
大変ありがたいことに、AutoMapperにはINamingConventionインターフェースによる変換ロジックの差し替え機能が用意されているので、今回はコレを利用してみます。
public class UpperUnderscoreNamingConvention : INamingConvention { //--- 分割に利用する文字列を取得 public string SeparatorCharacter{ get{ return "_"; } } //--- 文字列分割を行うための正規表現 public Regex SplittingExpression{ get{ return new Regex(@"[\p{Lu}0-9]+(?=_?)"); } } }
変換先インスタンスのプロパティ名の解釈に上記の方法を利用するように設定します。
Mapper.Initialize(config =>
{
config.DestinationMemberNamingConvention = new UpperUnderscoreNamingConvention();
config.CreateMap<PascalCase, camelCase>();
config.CreateMap<PascalCase, UPPER_SNAKECASE>();
config.CreateMap<PascalCase, lower_snakecase>();
});
この設定を入れた状態で冒頭の検証を再度行うと、結果は以下のようになります。lower_snakecaseの代わりにUPPER_SNAKECASEへの変換ができるようになったことが確認できます。
/* PascalCase : 1984/11/06 1:23:45 camelCase : 1984/11/06 1:23:45 UPPER_SNAKECASE : 1984/11/06 1:23:45 lower_snakecase : 0001/01/01 0:00:00 */
今回のようにAutoMapper既定の変換ルールに沿わないものへの変換を行う場合は、INamingConventionインターフェースで楽できないか試してみてください。