メインコンテンツまでスキップ

第10章:実践!データの詰め替え(ViewModel / DTO / Entity)🔄✨

この章は、SoCの“壁”をちゃんと機能させるための必殺技だよ〜!🥷💖 結論から言うと…

**詰め替え(Mapping)=「責務の違う箱どうしを、直接つながないための保険」**🛡️✨


1) 今日のゴール🎯💡

できるようになること✅

  • ViewModel / DTO / Entity を **「用途の違う箱」**として説明できる📦📦📦
  • どこで詰め替えるのが安全か(置き場所)を決められる🧭
  • 手動マッピングで“壊れにくい形”が書ける✍️✨
  • (おまけ)AutoMapper などのツール導入の判断ができる🤔🛠️ ※AutoMapperは最近 .NET 10 対応の v16 が出てるよ (Jimmy Bogard)

2) まず3つの箱をイメージしよ〜📦✨

🖥️ ViewModel(画面の箱)

  • 画面表示に便利な形(例:stringで表示、ボタン状態、エラー文言)
  • UIの都合が入ってOK(むしろ入る)😙💕

例:郵便番号ハイフン入り、価格の「¥1,200」表示、入力中の未確定値など


🌍 DTO(外の世界の箱)

  • API/DB/ファイルなど 外部とやり取りするための形📡🗄️
  • JSONにしやすい、DBに保存しやすい、など “外の都合” が入る

例:snake_case、nullable多め、IDが文字列、日時がUTC文字列…などあるある😭


🧠 Entity(業務ルールの箱)

  • 業務のルールが守られる形(ここが中心❤️)
  • 画面都合・DB都合を混ぜない🙅‍♀️

例: 「金額はマイナス禁止」「ステータス遷移のルール」「割引条件」など


3) じゃあ「詰め替え」はどこでやるの?🧭✨

おすすめはだいたいココ👇

  • UI → UseCase に渡す前(ViewModel → コマンド/入力モデル → Entity)
  • 外部I/Oの境界(DTO ↔ Entity)
  • “中心(Entity)” を守るために、境界に変換所(Mapper)を置くイメージ🚧🏠

✨ポイント:Entityに「DTOを受け取るコンストラクタ」みたいなのを作ると、外部都合が内側に侵入しがちなので注意だよ〜!😵‍💫🌀


4) ハンズオン:ミニ例で「詰め替え」を体験しよ🧪💕

soc_cs_study_010_mapping_conveyor

題材は「商品編集画面」🛒✨ 画面で編集 → 業務ルールで検証 → 保存用DTOにしてDB/APIへ、って流れ!


4-1) Entity(業務ルールの箱)🧠🧱

public sealed class Product
{
public ProductId Id { get; }
public string Name { get; private set; }
public Money Price { get; private set; }

public Product(ProductId id, string name, Money price)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("商品名は必須です");
Id = id;
Name = name.Trim();
Price = price; // Money側で不正(負数など)を防ぐ想定
}

public void Rename(string newName)
{
if (string.IsNullOrWhiteSpace(newName)) throw new ArgumentException("商品名は必須です");
Name = newName.Trim();
}

public void ChangePrice(Money newPrice) => Price = newPrice;
}

public readonly record struct ProductId(Guid Value);

public readonly record struct Money(decimal Value)
{
public Money(decimal value)
{
if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "金額はマイナス不可");
Value = value;
}

public override string ToString() => Value.ToString("0.##");
}

💡ここが中心!UIの文字列とかDTOの都合は一切出てこないのが理想✨


4-2) ViewModel(画面の箱)🖥️🌸

public sealed class ProductEditViewModel
{
public string Id { get; set; } = ""; // 画面は string が便利
public string Name { get; set; } = "";
public string PriceText { get; set; } = ""; // 入力中は "1200" とか "1,200" とかある
public string? ErrorMessage { get; set; } // 画面表示用
}

4-3) DTO(外部の箱)🌍📦

例えば保存APIがこういう形だとするね:

public sealed class ProductDto
{
public string id { get; set; } = ""; // 外部都合で lower/snake だったりする
public string name { get; set; } = "";
public decimal price { get; set; }
}

5) 手動マッピング:まずは“王道の型”から👑✍️

5-1) ViewModel → Entity(入力を業務の形にする)🔄🧠

コツは 「変換で失敗したら、Entityを作らない」 だよ✅✨

public static class ProductMapping
{
public static Product ToEntity(this ProductEditViewModel vm)
{
// 1) ID変換
if (!Guid.TryParse(vm.Id, out var guid))
throw new ArgumentException("IDが不正です");

// 2) Price変換(画面の文字列 → decimal)
if (!decimal.TryParse(vm.PriceText.Replace(",", ""), out var priceValue))
throw new ArgumentException("価格が数字じゃないよ");

// 3) Entity生成(ここでルールが守られる)
return new Product(
new ProductId(guid),
vm.Name,
new Money(priceValue)
);
}
}

✨いいところ

  • 画面入力のグチャグチャ(カンマとか)を Entityに持ち込まない🙅‍♀️
  • 例外やエラーは 境界で止められる🧱

それぞれの箱に合った「型」で話すのが一番平和なんです🕊️✨

5-2) Entity → DTO(保存/通信の形にする)📡🗄️

public static class ProductDtoMapping
{
public static ProductDto ToDto(this Product entity)
=> new()
{
id = entity.Id.Value.ToString(),
name = entity.Name,
price = entity.Price.Value
};

public static Product ToEntity(this ProductDto dto)
=> new(
new ProductId(Guid.Parse(dto.id)),
dto.name,
new Money(dto.price)
);
}

5-3) リストの詰め替え(地味に超よく使う)📚✨

var dtos = products.Select(p => p.ToDto()).ToList();

6) 「詰め替えが面倒😵‍💫」が“保険”になる理由🛡️✨

詰め替えがあると、こうなる👇

  • UIを変えても Entity は守られる🖥️🚫🧠
  • DB/APIの都合が変わっても Entity は守られる🗄️🚫🧠
  • 影響範囲が Mapperに集まる(修正が一点で済む)🎯✨

「面倒だからやらない」は、だいたい後で もっと面倒になるやつ…!😇💥


7) よくある落とし穴(先に踏み抜きを防ぐよ)🧨😆

落とし穴1:ViewModelに業務ルールを書き始める😵‍💫

  • 「割引計算を画面で…」みたいなのは危険⚠️ → 画面は 呼び出すだけ が基本だよ🖥️➡️🧠

落とし穴2:EntityがDTOに依存しちゃう📦➡️🧠

  • Entityが ProductDto を知り始めると、外部都合が侵入😱 → Mapperで止めよう🧱✨

落とし穴3:日時・通貨・Enumの変換が雑🕒💴🎛️

  • UTC/ローカル混在で事故る💥
  • 金額の丸めが揺れる💥
  • Enumの数値が外部で増えて落ちる💥 → 変換は Mapperに集約テストが最強🧪✨

8) AutoMapperはいつ使う?(“魔法”の扱い方)🪄🤔

AutoMapperは 手書きの量を減らせる便利ツールだけど、最近の状況として:

  • .NET 10対応を含む v16 がリリースされてるよ (Jimmy Bogard)
  • そして **商用版(デュアルライセンス)**の流れがあるよ(RPL 1.5 と商用ライセンス) (Jimmy Bogard)

だから教材としてのおすすめはこう👇

まず手動がおすすめな理由🙆‍♀️✨

  • “何がどこで変換されてるか”が見える👀
  • 変換の例外・丸め・null対応を自分で管理できる🧯
  • SoCの感覚が育つ🌱

AutoMapperが向くタイミング✅

  • 変換が大量で、手動がボトルネックになってきた
  • チームで「運用ルール」と「テスト方針」が決まってる
  • ライセンス面も含めて採用判断できる

9) (参考)“生成系Mapper”という選択肢もあるよ🤖⚙️

最近は Source Generatorでマッピングコードを生成するタイプも人気✨ 例えば Mapperly は「ビルド時にマッピングコードを生成して、実行時オーバーヘッドを小さくできる」系だよ (GitHub)

でも超入門の今は、まず手動で感覚を作ってからでOK🙆‍♀️💕


10) AI(Copilot/Codex)で詰め替えを爆速にするコツ🤖💨✨

AIに丸投げすると事故るので、お願いの仕方テンプレいくね💖

プロンプト例①:手動Mapperを作らせる✍️

C#で、ProductEditViewModel -> Product(Entity) の手動マッピングを作って。
条件:
- Id は Guid.TryParse で検証
- PriceText はカンマ除去して decimal.TryParse
- 失敗時は ArgumentException
- Entityのルールは Product/Money 側に寄せる
- Mapperは static クラス + 拡張メソッド
コードだけ出して

プロンプト例②:漏れチェック✅

次の3クラス間のマッピングで、変換漏れ・危険な変換(null、丸め、enum)をレビューして。
改善案も箇条書きで。

AIの提案を採用する前のチェック✅👀

  • 変換失敗時に Entity を作ってない?(中途半端生成してない?)
  • 画面の都合が Entity に入ってない?
  • 例外メッセージやエラーがUIに流せる形?
  • 金額・日時・Enumが雑じゃない?

11) ミニ課題(やると一気に身につくよ)🎮✨

課題A:入力の詰め替え✍️

  • UserEditViewModel { EmailText, AgeText } から User(Entity)(Email形式チェック、年齢0以上)を作ってみよ📩🎂

課題B:ネスト構造の詰め替え🏠

  • AddressViewModelAddress(Entity) を追加して User の中に入れよう!

課題C:詰め替えテスト🧪

  • 変換成功ケース / 失敗ケース(ID不正、価格不正)をテストにして、 “境界で止まる” を体験しよ✨

まとめ🌸✨

  • ViewModel / DTO / Entity は 責務が違う箱📦💖
  • 詰め替え(Mapping)は境界に置くとSoCが強くなる🧱✨
  • 最初は手動でOK!→ 慣れたら AutoMapper や生成系も検討🛠️ (AutoMapperは .NET 10対応の更新がありつつ、商用ライセンス面の確認も大事だよ) (Jimmy Bogard)
  • 開発環境的には Visual Studio 2026 / .NET 10 / C# 14 の流れが来てるよ (Microsoft Learn)

次の章(第11章)は、ここで作った「境界」をさらに強くする **依存の向き(DIPの入口)**に進むよ〜🧲🌟 この章の例を「WinForms版/WPF版のイベント → UseCase呼び出し」まで広げたサンプルも、必要なら同じノリで作るよ😊💖