Mapster是一个高性能的用于对象映射的类库,同类型的产品还有AutoMapper。它提供了一系列的API和工具,以下为几个重要的类和接口:
Mapster与AutoMapper的性能对比:
方法 | 平均值 | 标准偏差 | 错误 | 第0代 | 第1代 | 第2代 | 分配 |
---|---|---|---|---|---|---|---|
“Mapster 6.0.0” | 108.59毫秒 | 1.198毫秒 | 1.811毫秒 | 31000 | - | - | 124.36 MB |
“Mapster 6.0.0(罗斯林)” | 38.45毫秒 | 0.494毫秒 | 0.830毫秒 | 31142.8571 | - | - | 124.36 MB |
'Mapster 6.0.0(FEC)' | 37.03毫秒 | 0.281毫秒 | 0.472毫秒 | 29642.8571 | - | - | 118.26 MB |
'Mapster 6.0.0(Codegen)' | 34.16毫秒 | 0.209毫秒 | 0.316毫秒 | 31133.3333 | - | - | 124.36 MB |
“ExpressMapper 1.9.1” | 205.78毫秒 | 5.357毫秒 | 8.098毫秒 | 59000 | - | - | 236.51 MB |
“AutoMapper 10.0.0” | 420.97毫秒 | 23.266毫秒 | 35.174毫秒 | 87000 | - | - | 350.95 MB |
PM> Install-Package Mapster
或者 dotnet add package Mapster
目的:使用 Mapster 实现 User 到 UserDto 的映射
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Sex { get; set; }
public string like { get; set; }
}
public class UserDto
{
public string name { get; set; }
public int UserAge { get; set; }
public string UserSex { get; set; }
public string like { get; set; }
}
/*
* 默认情况下,无需任何配置,Mapster会根据两个实体字段名称相同进行匹配
* 第一次调用时,配置会被缓存,第二次将会从缓存中取,以此提升性能
*/
var user = new User();
var dto = user.Adapt<UserDto>();//映射为新对象
user.Adapt(dto);//在目标对象的基础上进行映射
//注意:Adapt扩展方法使用的配置为 `TypeAdapterConfig.GlobalSettings`
可以直接使用 Mapster 内置的全局静态配置 TypeAdapterConfig.GlobalSettings,也可以实例化一个配置 new TypeAdapterConfig()推荐使用实例化的方式,对 TypeAdapterConfig 进行映射配置。
注:Mapster 默认匹配规则是相同字段名之间进行映射。
直接在 TypeAdapterConfig 配置对象的映射关系
var config = new TypeAdapterConfig();
//映射规则
config.ForType<User, UserDto>()
.Map(dest => dest.UserAge, src => src.Age)
.Map(dest => dest.UserSex, src => src.Sex);
var mapper = new Mapper(config);//务必将mapper设为单实例
var user = new User{Name = "xiaowang",Age = 18,Sex = "boy"};
var dto = mapper.Map<UserDto>(user);
字段带有前后缀,可以使用 NameMatchingStrategy.ConvertDestinationMemberName对目标字段名称进行替换,使得它和源字段名称相同。还有替换源字段的方法NameMatchingStrategy.ConvertSourceMemberName
注意:如果一个ForType定义多个NameMatchingStrategy,后定义的会覆盖先定义的规则,所以只有最后定义的规则会生效
var config = new TypeAdapterConfig();
//使用
config.ForType<User, UserDto>()
.NameMatchingStrategy(NameMatchingStrategy.ConvertDestinationMemberName(dest => dest.Replace("User", "")));
使用接口的方式,需要实现 IRegister
//实现接口 IRegister
public class UserDtoRegister : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.ForType<User,UserDto>()
Map(dest => dest.UserAge, src => src.Age);
//...
}
}
//实例化Mapper
var config = new TypeAdapterConfig();
//var config = TypeAdapterConfig.GlobalSettings;
//只有要给定 IRegister 所在的程序集名称,Mapster 会自动识别 IRegister,进行配置注入。
config.Scan("程序集名称1","程序集名称2");
var mapper = new Mapper(config);//务必设置为单实例
var config = new TypeAdapterConfig();
//映射规则
config.ForType<User, UserDto>()
.Map(dest => dest.UserAge, src => src.Age)
.Map(dest => dest.UserSex, src => src.Sex);
.IgnoreNullValues(true)//忽略空值映射
.Ignore(dest => dest.UserAge)//忽略指定字段
.IgnoreAttribute(typeof(DataMemberAttribute))//忽略指定特性的字段
.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase)//忽略字段名称的大小写
.IgnoreNonMapped(true);//忽略除以上配置的所有字段
config.ForType<User,UserDto>()
.IgnoreMember((member, side) => !member.Type.Namespace.StartsWith("System"));//实现更细致的忽略规则
member和side分别对应IMemberModel和MemberSide,这里我贴出相应的源码。
//包含了映射类型的信息
public interface IMemberModel
{
Type Type { get; }
string Name { get; }
object? Info { get; }
AccessModifier SetterModifier { get; }
AccessModifier AccessModifier { get; }
IEnumerable<object> GetCustomAttributes(bool inherit);
}
//标识当前是源类型还是目标类型
public enum MemberSide
{
Source = 0,
Destination = 1
}
Mapster 的 Fork 功能允许我们定义局部的映射规则,并且分支不会重复编译,不需要考虑性能问题。
var config = new TypeAdapterConfig();
var mapper = new Mapper(config);
var user = new User{Name = "xiaowang",Age = 18,Sex = "boy"};
var dto = mapper.From(user).ForkConfig(forked =>
{
//该分支规则,不会重复编译,仅限该语句中有效,不会影响config的配置
forked.ForType<User, UserDto>().Map(dest => dest.name, src => src.Name);
})
.AdaptToType<UserDto>();//映射为新对象
dto = mapper.From(user).ForkConfig(forked =>
{
forked.ForType<User, UserDto>().Map(dest => dest.name, src => src.Name);
})
.AdaptTo(new UserDto());//在目标对象基础上进行映射
NewConfig 方法允许我们对两个类型之间新建配置,如果两个类型之前配置了映射关系,则 NewConfig 方法会覆盖之前的配置
var config = new TypeAdapterConfig();
config.ForType<User,UserDto>().Map(dest => dest.UserAge, src => src.Age);
//...
//覆盖 User 和 UserDto 之前的配置
config.NewConfig<User,UserDto>().Map(dest=>dest.UserAge,src=>100);
//扩展知识:覆盖Mapster默认静态配置
TypeAdapterConfig<User,UserDto>.NewConfig().Default.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);
允许运行时传入数据,干预映射过程
var config = new TypeAdapterConfig();
config.ForType<User, UserDto>()
.Map(dest => dest.name, src => MapContext.Current.Parameters["userName"]);//配置运行时参数
var mapper = new Mapper(config);
//使用时传入数据
var user = new User();
var dto = mapper.From(user).BuildAdapter().AddParameters("userName","xiaowang").AdaptToType<UserDto>();
我们尽量不要把实体间的映射规则配置到 TypeAdapterConfig.GlobalSettings (默认配置)。随着业务的发展,一个配置很难兼顾所有业务,可能会出行冲突的情况,相对复杂的业务,可以新建一个TypeAdapterConfig,或者使用 config.Clone()能轻松复制一份配置。全局配置可以放一些简单的配置项,例如:映射时忽略大小写。
注意:Adapt 扩展方法使用的是 TypeAdapterConfig.GlobalSettings
Dictionary<string,object> dict = new User().Adapt<Dictionary<string,object>>();//object 到 Dictionary 的转换
string s = 123.Adapt<string>(); //equal to 123.ToString();
int i = "123".Adapt<int>(); //equal to int.Parse("123");
BeforeMapping方法和 AfterMapping
//BeforeMapping 映射执行前
config.ForType<User, UserDto>().BeforeMapping((src, dest) =>
{
src.Age = 100;
dest.UserAge = src.Age + 10;
});
//AfterMapping 映射执行后
//...
C# Mapster 对象映射器学习
C#-对象映射器(二)-Mapster帮助类