前言
在專案中我們會經常遇到物件的對應,像是 Model 和 Dto 之間的對應,或是物件的深拷貝,這些都是需要我們自己實作的。此時,專案中會出現很多初始化物件的程式碼,這些程式碼寫起來相當枯燥乏味,那麼有沒有什麼辦法減輕我們的工作量,讓我們可以把時間花費在業務功能上呢?
目前 .Net 中的物件對應框架,功能強大且效能極佳的物件對應框架已經存在,其中使用最多的有:
說到物件對應框架,大家想到的最多的是 AutoMapper,可能很多人連 Mapster 都沒聽過,但不可否認的是 Mapster 確實是一個很好的物件對應框架,但由於中文文件的缺失,導致在國內知名度不是很高,今天我們就來介紹一下 Mapster 提供了哪些功能,如何在專案中使用它,Masa 提供的 Mapster 又做了什麼?
Mapster 簡介
Mapster 是一個使用簡單,功能強大的物件對應框架,自 2014 年開源到現在已經過去 8 個年頭,截止到現在,github 上已經擁有 2.6k 的 star,並保持著每年 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>(); //equal to (decimal)123;
Console.WriteLine($"結果為:{i == int.Parse(res)}");
執行控制檯程式:

列舉型別
- 把列舉對應到數字型別,同樣也支援字串到列舉和列舉到字串的對應,比 .NET 的預設實作快兩倍
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,歡迎聯絡我們
