AutoMapper のライセンスが商用化されるという話を聞いて、なんとなく自分で作ってみたらどうなるだろうかと思って作ってみた
基本的な使い方
単純なオブジェクトのマッピング
public class SourceData
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public class DestinationData
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
using MappingTool.Mapping;
var source = new SourceData { Id = 1, Name = "Test", Description = "This is a test." };
var mapper = new MapperFactory().CreateMapper<SourceData, DestinationData>();
var destination = mapper.Map(source);
Console.WriteLine($"Id: {destination.Id}, Name: {destination.Name}");
特徴
- Expression による割と高速な処理
- データ構造を問わない
- プロパティ名と型による自動マッピング
できること
1. 単純なオブジェクト同士のマッピング
- プロパティ名と型が一致する場合、自動的にマッピングを行います。
- プリミティブ型や基本的な型(
int
,string
,DateTime
など)に対応。
2. 階層構造のマッピング
- ネストされたオブジェクト(プロパティがクラスや構造体)の再帰的なマッピング。
- 循環参照が発生する場合でも無限ループに陥らないように設計されています。
3. class
, struct
, record
の対応
- クラス型、構造体型、レコード型を区別して適切にマッピング。
- レコード型では主コンストラクタ(
Primary Constructor
)を優先的に使用。
4. 循環参照の安全な処理
- 循環参照を検出し、プレースホルダを利用して安全に処理。
- 参照の一貫性を保ちながら、再帰的なマッピングを実現。
5. コンストラクタの識別
- デフォルトコンストラクタ、コピーコンストラクタ、引数付きコンストラクタを識別。
- 適切なコンストラクタを選択してオブジェクトを初期化。
6. AutoMapper のデフォルト設定に近い挙動
- AutoMapper のデフォルト設定と同様に、プロパティ名と型が一致する場合にマッピングを行います。
- 特別な設定なしで、基本的なマッピングが可能。
できないこと
1. マッピングのカスタマイズ
- 特定のプロパティを無視する、または特定のプロパティに対してカスタムロジックを適用する機能がありません。
- 例:
mapper.ForMember(dest => dest.Property, opt => opt.MapFrom(src => src.OtherProperty))
のような設定ができません。
2. プロパティの型変換
- プロパティの型が異なる場合、自動的に型変換を行う機能がありません。
- 例:
string
→int
やDateTime
→string
のような型変換はサポートされていません。
3. 特殊なオブジェクトのマッピング
-
Uri
クラスのように、デフォルトコンストラクタもコピーコンストラクタも持たないオブジェクトのマッピングができません。 - これらの型に対しては、カスタムロジックを追加する必要があります。
Entity Framework と EntityFrameworkCore のエンティティからのマッピングでの注意点
- トラッキングと参照の保持: EF の ChangeTracker はナビゲーションプロパティの参照を管理します。MapperFactory の preserveReferences を使う際、エンティティとそのナビゲーションの参照整合性に注意してください。マッピング時に DB エンティティを直接変更するとトラッキングに影響する可能性があります。
- Lazy-loading / プロキシ: EF Core の Lazy Loading や EF6 のプロキシは型が動的に変わるため、リフレクションや型チェックで想定外の型が渡される場合があります。必要ならマッピング前に context.Entry(entity).Reference(...).Load() などで明示的に読み込むか、プロキシを解除して POCO に変換してください。
- シリアライズとナビゲーション: 循環参照を持つナビゲーションをそのままシリアライズするとループします。preserveReferences はマッピングレベルで同一参照を保てますが、JSON シリアライズや外部 APIs 向けに出力する際は DTO に切り分けることを推奨します。
- DbContext の寿命: 短命な DbContext(1 リクエスト/操作ごと)を推奨します。長寿命のコンテキストを使うと ChangeTracker の情報が拡張し、メモリや参照の扱いに影響します。
- マップ先の更新: エンティティを直接マップ先として使い、そのまま SaveChanges() すると意図しない更新が発生することがあります。変更を明確にするために DTO を経由するか、明示的に EntityState を設定してください。