オブジェクトマッピング - Mapping.Mapster

オブジェクトマッピング - Mapping.Mapster

プロジェクトでは、ModelとDtoの間のマッピングやオブジェクトのディープコピーなど、オブジェクトのマッピングに頻繁に遭遇します。これらはすべて自分で実装する必要があります。

最終更新 2022/07/06 20:18
磊_磊
読了目安 5 分
カテゴリ
.NET
タグ
.NET C#

はじめに

プロジェクトでは、ModelとDtoの間のマッピングやオブジェクトのディープコピーなど、オブジェクトのマッピングを頻繁に実装する必要があります。その都度、自分で実装しなければなりません。そうすると、プロジェクト内にオブジェクトを初期化するコードが大量に発生し、非常に退屈で面倒です。では、作業量を減らし、業務機能に時間を費やせる方法はないでしょうか?

現在、.NETには強力でパフォーマンスの優れたオブジェクトマッピングフレームワークが存在し、その中でも最もよく使われているのは以下のものです。

オブジェクトマッピングフレームワークと言えば、多くの方がAutoMapperを思い浮かべるでしょう。Mapsterを知らない方も多いかもしれません。しかし、Mapsterは確かに優れたオブジェクトマッピングフレームワークですが、中国語ドキュメントが不足しているため、国内での知名度は高くありません。本日は、Mapsterが提供する機能、プロジェクトでの使用方法、そしてMasaが提供するMapsterが何をしているのかを紹介します。

Mapster の概要

Mapsterは、シンプルで強力なオブジェクトマッピングフレームワークです。2014年にオープンソース化されてから8年が経ち、現在ではGitHubで2.6kのスターを獲得し、年に3回のリリース頻度を維持しています。その機能はAutoMapperと類似しており、オブジェクト間のマッピングやIQueryableからオブジェクトへのマッピングをサポートします。AutoMapperと比較して、速度とメモリ使用量で優れており、1/3のメモリで4倍のパフォーマンス向上が可能です。では、実際にMapsterをどのように使うのか見てみましょう。

準備

コンソールプロジェクトAssignment.Mapsterを作成し、Mapsterをインストールします。

dotnet add package Mapster --version 7.3.0

新しいオブジェクトへのマッピング

  1. クラスUserDtoを作成
public class UserDto
{
    public int Id { get; set; }

    public string Name { get; set; }

    public uint Gender { get; set; }

    public DateTime BirthDay { get; set; }
}
  1. 匿名オブジェクトを作成(変換元のオブジェクト)
var user = new
{
    Id = 1,
    Name = "Tom",
    Gender = 1,
    BirthDay = DateTime.Parse("2002-01-01")
};
  1. user 元オブジェクトをターゲットオブジェクト(UserDto)にマッピング
var userDto = user.Adapt<UserDto>();
Console.WriteLine($"新しいオブジェクトにマッピング、Name: {userDto.Name}");

コンソールを実行して変換成功を確認:

新しいオブジェクトにマッピング

データ型

オブジェクト間のマッピングに加えて、データ型の変換もサポートしています。例:

基本型

  • Convert.ChangeType() と同様の型変換機能を提供
string res = "123";
decimal i = res.Adapt<decimal>(); // (decimal)123 と同等
Console.WriteLine($"結果:{i == int.Parse(res)}");

コンソールを実行:

基本型変換

列挙型

  • 列挙型を数値型にマッピング。文字列から列挙型、列挙型から文字列へのマッピングもサポート。.NETのデフォルト実装より2倍高速
var fileMode = "Create, Open".Adapt<FileMode>(); // FileMode.Create | FileMode.Open と同等
Console.WriteLine($"列挙型変換の結果:{fileMode == (FileMode.Create | FileMode.Open)}");

コンソールを実行して変換成功を確認:

列挙型変換

Queryable 拡張

Mapster は Queryable の拡張を提供し、DbContext の必要な条件での検索を実現します。例:

  1. クラスUserDbContextを作成
using Assignment.Mapster.Domain;
using Microsoft.EntityFrameworkCore;

namespace Assignment.Mapster.Infrastructure;

public class UserDbContext : DbContext
{
    public DbSet<User> User { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var dataBaseName = Guid.NewGuid().ToString();
        optionsBuilder.UseInMemoryDatabase(dataBaseName); // インメモリデータベースを使用(テスト用)
    }
}
  1. クラスUserを作成
public class User
{
    public int Id { get; set; }

    public string Name { get; set; }

    public uint Gender { get; set; }

    public DateTime BirthDay { get; set; }

    public DateTime CreationTime { get; set; }

    public User()
    {
        CreationTime = DateTime.Now;
    }
}
  1. Queryable 拡張メソッドProjectToTypeを使用
using (var dbContext = new UserDbContext())
{
    dbContext.Database.EnsureCreated();

    dbContext.User.Add(new User()
    {
        Id = 1,
        Name = "Tom",
        Gender = 1,
        BirthDay = DateTime.Parse("2002-01-01")
    });
    dbContext.SaveChanges();

    var userItemList = dbContext.User.ProjectToType<UserDto>().ToList();
}

コンソールを実行して変換成功を確認:

Queryable拡張

この他にも、Mapsterはマッピング前後処理、コピーとマージ、マッピング設定のネストサポートなどを提供しています。詳細はドキュメントを参照してください。Mapsterがこれほど強力なら、直接それを使用すればよいのではないでしょうか? なぜMasaが提供するMapperを使う必要があるのでしょうか?

Masa.Contrib.Data.Mapping.Mapster とは?

Masa.Contrib.Data.Mapping.Mapsterは、Mapsterをベースとしたオブジェクト間のマッパーです。元のMapsterに加えて、最適なコンストラクタを自動的に取得してマッピングに使用する機能と、ネストしたマッピングのサポートを追加し、マッピングの作業量を軽減します。

マッピングルール

  • ターゲットオブジェクトにコンストラクタがない場合:空のコンストラクタを使用し、フィールドとプロパティにマッピングします。
  • ターゲットオブジェクトに複数のコンストラクタがある場合:最適なコンストラクタを取得してマッピングします。

最適なコンストラクタ: ターゲットオブジェクトのコンストラクタパラメータ数を大きいものから降順に検索し、パラメータ名が一致(大文字小文字を区別しない)し、パラメータ型が元オブジェクトのプロパティと一致するコンストラクタ。

準備

コンソールプロジェクトAssignment.Masa.Mapsterを作成し、Masa.Contrib.Data.Mapping.MapsterMicrosoft.Extensions.DependencyInjectionをインストールします。

dotnet add package Masa.Contrib.Data.Mapping.Mapster --version 0.4.0-rc.4
dotnet add package Microsoft.Extensions.DependencyInjection --version 6.0.0
  1. クラスOrderItemを作成
public class OrderItem
{
    public string Name { get; set; }

    public decimal Price { get; set; }

    public int Number { get; set; }

    public OrderItem(string name, decimal price) : this(name, price, 1)
    {

    }

    public OrderItem(string name, decimal price, int number)
    {
        Name = name;
        Price = price;
        Number = number;
    }
}
  1. クラスOrderを作成
public class Order
{
    public string Name { get; set; }

    public decimal TotalPrice { get; set; }

    public List<OrderItem> OrderItems { get; set; }

    public Order(string name)
    {
        Name = name;
    }

    public Order(string name, OrderItem orderItem) : this(name)
    {
        OrderItems = new List<OrderItem> { orderItem };
        TotalPrice = OrderItems.Sum(item => item.Price * item.Number);
    }
}
  1. クラスProgramを変更
using Assignment.Masa.Mapster.Domain.Aggregate;
using Masa.BuildingBlocks.Data.Mapping;
using Masa.Contrib.Data.Mapping.Mapster;
using Microsoft.Extensions.DependencyInjection;

Console.WriteLine("Hello Masa Mapster!");

IServiceCollection services = new ServiceCollection();
services.AddMapping();

var request = new
{
    Name = "Teach you to learn Dapr ……",
    OrderItem = new OrderItem("Teach you to learn Dapr hand by hand", 49.9m)
};
var serviceProvider = services.BuildServiceProvider();
var mapper = serviceProvider.GetRequiredService<IMapper>();
var order = mapper.Map<Order>(request);

Console.WriteLine($"{nameof(Order.TotalPrice)} is {order.TotalPrice}"); // 出力:49.9

Console.ReadKey();

変換が成功すれば、TotalPrice の値は 49.9 になるはずです。コンソールを実行して変換を確認:

Mapping.Mapster

実現方法

上記でMasa.Contrib.Data.Mapping.Mapsterは、最適なコンストラクタを自動取得してマッピングに使用し、オブジェクト間のマッピングを完了できると述べました。では、どのように実現しているのでしょうか? パフォーマンスに影響はあるのでしょうか?

最適なコンストラクタを自動取得して使用する機能は、Mapsterが提供するコンストラクタマッピング機能を利用しています。コンストラクタを指定することで、オブジェクト間のマッピングを完了します。

ドキュメントを参照

まとめ

現在のMasa.Contrib.Data.Mapping.Mapsterの機能は比較的弱く、現行バージョンではMapsterと比較して、最適なコンストラクタを自動取得して使用する機能が追加されただけです。これにより、空のコンストラクタがなく、複数のコンストラクタを持つクラスに対しても、余分なコードを書くことなくマッピングを完了できます。

しかし、Masa版のMapping最大の利点は、プロジェクトがBuildingBlocksIMapperに依存していることです。Mapsterそのものではありません。これにより、プロジェクトと具体的なマッパー実装が分離されます。もしプロジェクトでAutoMapperを強制的に使用する必要がある場合、AutoMapper版のIMapperを実装するだけで、業務コードを大きく変更する必要はなく、参照するパッケージを変更するだけで済みます。これがBuildingBlocksの魅力でもあります。

本章のソースコード

Assignment04:https://github.com/zhenlei520/MasaFramework.Practice

オープンソースアドレス

MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks

MASA.Contrib:https://github.com/masastack/MASA.Contrib

MASA.Utils:https://github.com/masastack/MASA.Utils

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

私たちのMASA Frameworkに興味があれば、コードの貢献、使用、Issueの提出など、お気軽にご連絡ください。

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む