読者です 読者をやめる 読者になる 読者になる

xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

AutoMapperで特定の命名規則で変換する

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(@"[&#92;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インターフェースで楽できないか試してみてください。