はじめに
プロジェクトでは、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
新しいオブジェクトへのマッピング
- クラス
UserDtoを作成
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public uint Gender { get; set; }
public DateTime BirthDay { get; set; }
}
- 匿名オブジェクトを作成(変換元のオブジェクト)
var user = new
{
Id = 1,
Name = "Tom",
Gender = 1,
BirthDay = DateTime.Parse("2002-01-01")
};
- 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 の必要な条件での検索を実現します。例:
- クラス
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); // インメモリデータベースを使用(テスト用)
}
}
- クラス
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;
}
}
- 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();
}
コンソールを実行して変換成功を確認:

この他にも、Mapsterはマッピング前後処理、コピーとマージ、マッピング設定のネストサポートなどを提供しています。詳細はドキュメントを参照してください。Mapsterがこれほど強力なら、直接それを使用すればよいのではないでしょうか? なぜMasaが提供するMapperを使う必要があるのでしょうか?
Masa.Contrib.Data.Mapping.Mapster とは?
Masa.Contrib.Data.Mapping.Mapsterは、Mapsterをベースとしたオブジェクト間のマッパーです。元のMapsterに加えて、最適なコンストラクタを自動的に取得してマッピングに使用する機能と、ネストしたマッピングのサポートを追加し、マッピングの作業量を軽減します。
マッピングルール
- ターゲットオブジェクトにコンストラクタがない場合:空のコンストラクタを使用し、フィールドとプロパティにマッピングします。
- ターゲットオブジェクトに複数のコンストラクタがある場合:最適なコンストラクタを取得してマッピングします。
最適なコンストラクタ: ターゲットオブジェクトのコンストラクタパラメータ数を大きいものから降順に検索し、パラメータ名が一致(大文字小文字を区別しない)し、パラメータ型が元オブジェクトのプロパティと一致するコンストラクタ。
準備
コンソールプロジェクトAssignment.Masa.Mapsterを作成し、Masa.Contrib.Data.Mapping.MapsterとMicrosoft.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
- クラス
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;
}
}
- クラス
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);
}
}
- クラス
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 になるはずです。コンソールを実行して変換を確認:

実現方法
上記でMasa.Contrib.Data.Mapping.Mapsterは、最適なコンストラクタを自動取得してマッピングに使用し、オブジェクト間のマッピングを完了できると述べました。では、どのように実現しているのでしょうか? パフォーマンスに影響はあるのでしょうか?
最適なコンストラクタを自動取得して使用する機能は、Mapsterが提供するコンストラクタマッピング機能を利用しています。コンストラクタを指定することで、オブジェクト間のマッピングを完了します。
ドキュメントを参照
まとめ
現在のMasa.Contrib.Data.Mapping.Mapsterの機能は比較的弱く、現行バージョンではMapsterと比較して、最適なコンストラクタを自動取得して使用する機能が追加されただけです。これにより、空のコンストラクタがなく、複数のコンストラクタを持つクラスに対しても、余分なコードを書くことなくマッピングを完了できます。
しかし、Masa版のMapping最大の利点は、プロジェクトがBuildingBlocksのIMapperに依存していることです。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の提出など、お気軽にご連絡ください。
