Mapster (C# 对象映射器)
参考:https://www.cnblogs.com/qiqigou/p/13696669.html
官方文档:https://github.com/MapsterMapper/Mapster/wiki
前言
谈到对象映射器,AutoMapper 知名度是非常的高,但很少有人知道 Mapster。性能优于 AutoMapper
安装 Mapster
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)
默认情况下,无需任何配置,Mapster会根据两个实体字段名称相同进行匹配。
如果需要更复杂的对象映射就需要通过TypeAdapterConfig配置
TypeAdapterConfig.GlobalSettings.ForType<User, UserDto>()
//.Ignore(dest => dest.Age);//排除Age属性映射
//.IgnoreMember((memberModel, memberSide) => memberModel.Name == "Name")//排除Name属性映射,相比Ignore()可以实现更细致的忽略规则
//.IgnoreIf((src, desc) => src.Age == 18, desc => desc.Age)//如果年龄等于18岁就排除Age属性映射
//.IgnoreIf((src, desc) => src.Age == 18, "Name", "Age");//如果年龄等于18岁就排除Name、Age属性映射
//.IgnoreNullValues(true);//如果是属性是空值就排除属性映射
//.IgnoreAttribute(typeof(ExcludeAttribute));//排除标记了ExcludeAttribute的属性映射
// .Map(desc => desc.Age, source => source.Age + 10);//指定属性映射规则
//.IgnoreNonMapped(true);//只映射Map指定的属性,其他属性都排除
//.NameMatchingStrategy(NameMatchingStrategy.IgnoreCase);//忽略字段名称的大小写
//映射执行前执行
TypeAdapterConfig.GlobalSettings.ForType<User, UserDto>().BeforeMapping((source, desc) =>
{
desc.age = source.Age;
});
//映射执行后执行
TypeAdapterConfig.GlobalSettings.ForType<User, UserDto>().AfterMapping((source, desc) =>
{
desc.age = source.Age;
//desc.Users = source.Adapt<UserDto>();
});
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
}
几种配置方式
两种配置方式:全局配置TypeAdapterConfig.GlobalSettings
、实例化一个配置new TypeAdapterConfig()
我们尽量不要把实体间的映射规则配置到 TypeAdapterConfig.GlobalSettings (默认配置)。随着业务的发展,一个配置很难兼顾所有业务,可能会出现冲突的情况,相对复杂的业务,可以新建一个TypeAdapterConfig,或者使用 config.Clone()能轻松复制一份配置。全局配置可以放一些简单的配置项,例如:映射时忽略大小写。
注意:Adapt 扩展方法使用的是 TypeAdapterConfig.GlobalSettings
方式一:全局配置
全局静态配置是Mapster内置的
TypeAdapterConfig.GlobalSettings.ForType<User, UserDto>();
方式二:实例化配置
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);
方式三: 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);//务必设置为单实例
方式四:依赖注入
安装包:PM> Install-Package Mapster.DependencyInjection
startup中注册TypeAdapterConfig
、ServiceMapper
public void ConfigureServices(IServiceCollection services)
{
...
var config = new TypeAdapterConfig();
// Or
// var config = TypeAdapterConfig.GlobalSettings;
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
}
注册服务后,可以使用IMapper映射对象
public class FooService {
private readonly IMapper _mapper;
public FooService(IMapper mapper) {
_mapper = mapper;
}
public void DoSomething(Poco poco) {
var dto = _mapper.Map<Dto>(poco);
}
}
注意:ServiceMapper的生命周期取决于你想要注入的服务。它可以是单例的,如果你只注入单例服务。如果任何注入的服务都是瞬态的,那么它也可以是瞬态的。
分支(Fork)
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 方法允许我们对两个类型之间新建配置,如果两个类型之前配置了映射关系,则 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>();
List转换
List<User> userList = new List<User> { user, new User { Name = "fan1", Age = 19 } };
var userDtoList = userList.Adapt<List<UserDto>>();
对象转字典
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");
映射到现有对象
创建对象后,Mapster会映射到该对象。
sourceObject.Adapt(destObject);
和 EFCore 配合
Mapster 还提供了 ProjectToType Linq 拓展方法减少我们手动 Select 操作,如:
正常的操作:
var destinations = context.Sources
.Select(p => new Destination {
Id = p.Id,
Name = p.Name,
Surname = p.Surname,
....
})
.ToList();
使用 Mapster 之后:
var destinations = context.Sources.ProjectToType<Destination>().ToList();