MVVM框架下的Mapster工具包

目录

引言

高性能对象映射框架 Mapster

1、简单映射

2、数据类型

2.1、基本类型

2.2、枚举类型

2.3、字符串类型

2.4、集合

2.5、可映射对象

3、映射配置

3.1、全局设置

3.2、基于条件的映射

3.3、未知源类型

3.4、泛型类型

4、映射配置的继承

4.1、源类型映射配置子类默认继承

 4.2、目标类型映射配置默认不继承

4.3、手动继承

5、配置实例

5.1、不同场景独立配置

 5.2、映射配置的复制与扩展

 5.3、配置入口和遗漏补充

6、不同程序集的映射配置

6.1、Scan

6.2、Apply

 7、映射验证和编译

7.1、验证映射配置是否存在错误

7.2、编译配置

8、自定义映射

8.1、自定义属性映射关系

8.2、条件映射

8.3、映射私有成员

8.4、多级映射

8.5、空值

8.6、多个源映射到同一个类型

 8.7、特性

AdaptIgnore 特性


引言

WPF应用程序的三层架构:Model->ViewModel->View。个人理解就是将后台的运行配置文件或则结果Model转换为可以与窗体交互的ViewModel,ViewModel 再与窗体进行深度绑定,实现后台与前端的解耦。ViewModel作为中间介质包含Model的开放属性,同时又有和View交互的衍生属性或则命令Command。Mapster提供了一种Model<->ViewModel的映射方法,通过Adapt扩展方法实现属性相互映射。

高性能对象映射框架 Mapster

Mapster的高性能可由以下对比列表得出:

MethodMeanStdDevErrorGen 0Gen 1Gen 2Allocated
'Mapster 6.0.0'108.59 ms1.198 ms1.811 ms31000.0000--124.36 MB
'Mapster 6.0.0 (Roslyn)'38.45 ms0.494 ms0.830 ms31142.8571--124.36 MB
'Mapster 6.0.0 (FEC)'37.03 ms0.281 ms0.472 ms29642.8571--118.26 MB
'Mapster 6.0.0 (Codegen)'34.16 ms0.209 ms0.316 ms31133.3333--124.36 MB
'ExpressMapper 1.9.1'205.78 ms5.357 ms8.098 ms59000.0000--236.51 MB
'AutoMapper 10.0.0'420.97 ms23.266 ms35.174 ms87000.0000--350.95 MB

对比其他框架占用更少的内存, 拥有更好的性能。

1、简单映射

默认会将两个类型中相同名称的属性进行映射

//映射到新对象
var destObject = sourceObject.Adapt<Destination>();
//映射到现有对象
sourceObject.Adapt(destObject);

扩展方法 Adapt,为避免装箱/拆箱 请使用第一个版本指定类型

var dest = src.Adapt<TSource, TDestination>();
var dest = src.Adapt<TDestination>();

2、数据类型

2.1、基本类型

基本类型的转换 ,例如: int/bool/dobule/decimal ,包括可空的基本类型。

只要C#支持类型转换的类型,那么在 Mapster 中也同样支持转换。

decimal i = 123.Adapt<decimal>(); //equal to (decimal)123

2.2、枚举类型

Mapster 会自动把枚举映射到数字类型,同样也支持 字符串到枚举 和 枚举到字符串的映射。

.NET 默认实现 枚举/字符串 转换非常慢,Mapster 比 .NET 的默认实现快两倍。

在 Mapster 中,字符串转枚举,如果字符串为空或空字符串,那么枚举将初始化为第一个枚举值。

在Mapster中,也支持标记的枚举。

var e = "Read, Write, Delete".Adapt<FileShare>();  
//FileShare.Read | FileShare.Write | FileShare.Delete

对于不同类型的枚举,Mapster 默认将值映射为枚举。调用 EnumMappingStrategy 方法可以指定枚举映射方式,如:

TypeAdapterConfig.GlobalSettings.Default
    .EnumMappingStrategy(EnumMappingStrategy.ByName);

2.3、字符串类型

在 Mapster 中,将其它类型映射为字符串时,Mapster 将调用类型的 ToString 方法。

如果将字符串映射为类型时,Mapster 将调用类型的 Parse 方法。

var s = 123.Adapt<string>(); // 等同于: 123.ToString();
var i = "123".Adapt<int>();  // 等同于: int.Parse("123");

2.4、集合

包括列表、数组、集合、包括各种接口的字典之间的映射: IList<T>, ICollection<T>, IEnumerable<T>, ISet<T>, IDictionary<TKey, TValue> 等等…

var list = db.Pocos.ToList();
var target = list.Adapt<IEnumerable<Dto>>();  

2.5、可映射对象

Mapster 可以使用以下规则映射两个不同的对象

  • 源类型和目标类型属性名称相同。 例如: dest.Name = src.Name
  • 源类型有 GetXXXX 方法。例如: dest.Name = src.GetName()
  • 源类型属性有子属性,可以将子属性的赋值给符合条件的目标类型属性,例如: dest.ContactName = src.Contact.Namedest.Contact_Name = src.Contact.Name
class Staff {
    public string Name { get; set; }
    public int GetAge() { 
        return (DateTime.Now - this.BirthDate).TotalDays / 365.25; 
    }
    public Staff Supervisor { get; set; }
    ...
}

struct StaffDto {
    public string Name { get; set; }
    public int Age { get; set; }
    public string SupervisorName { get; set; }
}

var dto = staff.Adapt<StaffDto>();  
//dto.Name = staff.Name, dto.Age = staff.GetAge(), dto.SupervisorName = staff.Supervisor.Name

可映射对象类型包括:

  • 结构体
  • 接口
  • 实现 IDictionary<string, T> 接口的字典类型
  • Record 类型 (类、结构体、接口)

对象转换为字典的例子:

var point = new { X = 2, Y = 3 };
var dict = point.Adapt<Dictionary<string, int>>();
dict["Y"].ShouldBe(3);

Record 类型的例子:

class Person {
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age) {
        this.Name = name;
        this.Age = age;
    }
}

var src = new { Name = "Mapster", Age = 3 };
var target = src.Adapt<Person>();

自动映射 Record 类型有一些限制:

  • Record 类型属性必须没有 set
  • 只有一个非空构造函数
  • 构造函数中的所有参数名称必须与属性名称相同

如果不符合以上规则,需要增加额外的 MapToConstructor 配置

3、映射配置

使用 TypeAdapterConfig<TSource, TDestination>.NewConfig()TypeAdapterConfig<TSource, TDestination>.ForType() 配置类型映射;

当调用 NewConfig 方法时,将会覆盖已存在的类型映射配置。

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .Ignore(dest => dest.Age)
    .Map(dest => dest.FullName,
        src => string.Format("{0} {1}", src.FirstName, src.LastName));

如若不想覆盖之前已经创建好的映射配置,可以使用 TypeAdapterConfig<TSource, TDestination>.ForType()

ForType 方法与 NewConfig 的差别:如果指定类型映射配置不存在,那它将创建一个新的映射,如果指定类型的映射配置已存在,那么它将会扩展已有的映射配置,而不是删除或替换已有的映射配置。

TypeAdapterConfig<TSource, TDestination>
        .ForType()
        .Ignore(dest => dest.Age)
        .Map(dest => dest.FullName,
            src => string.Format("{0} {1}", src.FirstName, src.LastName));

3.1、全局设置

使用全局设置将映射策略应用到所有的映射配置。

TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true);

对于特定的类型映射,你可以使用 TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig() 覆盖全局映射配置。

TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig().PreserveReference(false);

3.2、基于条件的映射

你可以使用 When 方法,当满足某个条件时,进行一些特定的映射操作。

下面的这个例子,当任何一个映射的 源类型和目标类型 相同时,不映射 Id 属性:

TypeAdapterConfig.GlobalSettings.When((srcType, destType, mapType) => srcType == destType)
    .Ignore("Id");

在下面这个例子中,映射配置只对 IQueryable 生效:

TypeAdapterConfig.GlobalSettings.When((srcType, destType, mapType) => mapType == MapType.Projection)
    .IgnoreAttribute(typeof(NotMapAttribute));

3.3、未知源类型

在不确定源类型的时候,使用 ForDestinationType 来创建针对于 目标类型 的映射配置。

比如使用 AfterMapping 在映射完成后调用目标类型对象的 Validate 方法:

TypeAdapterConfig.GlobalSettings.ForDestinationType<IValidator>()
                    .AfterMapping(dest => dest.Validate());

注意!在上面的代码段中指定目标类型为 IValidator 接口,那么将会把映射配置应用到所有实现了 IValidator 的类型。

3.4、泛型类型

如果映射的是泛型类型,可以通过将泛型类型传给 ForType 来创建设置.

TypeAdapterConfig.GlobalSettings.ForType(typeof(GenericPoco<>), typeof(GenericDto<>))
    .Map("value", "Value");

4、映射配置的继承

4.1、源类型映射配置子类默认继承

Mapster 默认会把 源类型的 映射配置 应用到 源类型的子类。

如创建了一个 SimplePoco -> SimpleDto 的映射配置:

TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
    .Map(dest => dest.Name, src => src.Name + "_Suffix");

那么继承了 SimplePocoDerivedPoco 也将应用同样的映射配置:

var dest = TypeAdapter.Adapt<DerivedPoco, SimpleDto>(src); 
//dest.Name = src.Name + "_Suffix"

如果不希望子类使用父类映射配置,可以设置 AllowImplicitSourceInheritancefalse 关闭继承:

TypeAdapterConfig.GlobalSettings.AllowImplicitSourceInheritance = false;

 4.2、目标类型映射配置默认不继承

Mapster 默认不会把 目标类型的 映射配置 应用到 目标类型的子类。

可以设置 AllowImplicitDestinationInheritance 开启:

TypeAdapterConfig.GlobalSettings.AllowImplicitDestinationInheritance = true;

4.3、手动继承

可以通过 Inherits 方法显示的继承类型映射配置:

TypeAdapterConfig<DerivedPoco, DerivedDto>.NewConfig()
        .Inherits<SimplePoco, SimpleDto>();

父类也可继承子类的映射关系

TypeAdapterConfig<Vehicle, VehicleDto>.NewConfig()
    .Include<Car, CarDto>();

Vehicle vehicle = new Car { Id = 1, Name = "Car", Make = "Toyota" };
var dto = vehicle.Adapt<Vehicle, VehicleDto>();

dto.ShouldBeOfType<CarDto>();
((CarDto)dto).Make.ShouldBe("Toyota"); //The 'Make' property doesn't exist in Vehicle

5、配置实例

5.1、不同场景独立配置

在 Mapster 中,默认的配置实例为 TypeAdapterConfig.GlobalSettings ,如果需要在不同场景下有不同的映射配置,Mapster 提供了 TypeAdapterConfig 用于实现此需求:

var config = new TypeAdapterConfig();
config.Default.Ignore("Id");

如何给 配置实例 添加l类型映射配置?

直接使用 NewConfigForType 方法即可:

config.NewConfig<TSource, TDestination>()
        .Map(dest => dest.FullName,
            src => string.Format("{0} {1}", src.FirstName, src.LastName));

config.ForType<TSource, TDestination>()
        .Map(dest => dest.FullName,
            src => string.Format("{0} {1}", src.FirstName, src.LastName));

注意! 配置实例 在程序中一定要作为单例存在,否则会影响性能! 

通过将 配置实例 作为参数传给 Adapt 方法来应用特定的类型映射配置:

var result = src.Adapt<TDestination>(config);

也可以创建一个指定 TypeAdapterConfigMapper,使用 Mapper 来做映射:

var mapper = new Mapper(config);
var result = mapper.Map<TDestination>(src);

 5.2、映射配置的复制与扩展

如果想从现有的配置中创建配置实例,可以使用 Clone 方法。

例如 复制全局配置实例 :

var newConfig = TypeAdapterConfig.GlobalSettings.Clone();
var newConfig = oldConfig.Clone();

Fork 方法内部直接调用 Clone 方法,但是使用 Fork 方法的形式与使用 Clone 方法有些许差别。

 Fork 可以内部直接进行映射扩展

var forked = mainConfig.Fork(config => 
				{
					config.ForType<Poco, Dto>()
						.Map(dest => dest.code, src => src.Id);
				});

var dto = poco.Adapt<Dto>(forked);

以上代码等同于使用 Clone 方法实现的以下代码:

var forked = mainConfig.Clone();
forked.ForType<Poco, Dto>()
      .Map(dest => dest.code, src => src.Id);

var dto = poco.Adapt<Dto>(forked);

 5.3、配置入口和遗漏补充

映射配置应该只初始化并且只进行一次配置。因此在编写代码的时候不能将映射配置和映射调用放在同一个地方。多次设置同一个配置会抛出异常,如下:

config.ForType<Poco, Dto>().Ignore("Id");
var dto1 = poco1.Adapt<Dto>(config);

config.ForType<Poco, Dto>().Ignore("Id"); //<--- 这里将抛出异常,因为在这之前已经触发过了映射
var dto2 = poco2.Adapt<Dto>(config);

一般会放在程序的入口,如 Main方法 Startup方法

但是难免在使用时会对映射配置进行补充,这时可通过Fork函数进行补充,不用担心性能问题,只有第一次使用配置实例时会编译,之后的调用将从缓存中获取。

var dto = poco.Adapt<Dto>(
	config.Fork(forked => forked.ForType<Poco, Dto>().Ignore("Id"));

6、不同程序集的映射配置

将映射配置分布在许多不同的程序集上是比较常见的。

6.1、Scan

允许扫描程序集中的这些规则会很有帮助,这样你就有一些基本的方法来组织规则,并且不会忘记调用注册码。在某些情况下,甚至可能需要按特定顺序注册程序集,以便某些规则优先于其他规则。装配扫描有助于实现这一点。

程序集扫描很简单,只需在程序集中创建任意数量的实现,然后从类中调用:IRegisterScanTypeAdapterConfig

public class MyRegister : IRegister
{
	public void Register(TypeAdapterConfig config)
	{
		config.NewConfig<TSource, TDestination>();

		//OR to create or enhance an existing configuration
		config.ForType<TSource, TDestination>();
	}
}

要在全局级别进行扫描和注册,请执行以下操作:

TypeAdapterConfig.GlobalSettings.Scan(assembly1, assembly2, assemblyN)

对于特定的配置实例:

var config = new TypeAdapterConfig();
config.Scan(assembly1, assembly2, assemblyN);

6.2、Apply

如果使用其他程序集扫描库(如MEF),则可以使用Apply方法轻松应用注册。

var registers = container.GetExports<IRegister>();
config.Apply(registers);

Apply方法还允许您有选择地从一个或多个而不是每个组件中进行拾取。

var register = new MockingRegister();
config.Apply(register);

 7、映射验证和编译

7.1、验证映射配置是否存在错误

调用 TypeAdapterConfig<Source, Destination>.NewConfg()Compile 方法将验证 特定类型的映射配置是否存在错误;

调用 配置实例 的 Compile 方法以验证 配置实例中的映射配置 是否存在错误;

另外,如果启用了 显式映射 , 它还将包含没有在映射器中注册的类的错误。

// 验证特定配置
var config = TypeAdapterConfig<Source, Destination>.NewConfig();
config.Compile();

// 验证整个配置实例的配置
TypeAdapterConfig<Source, Destination>.NewConfig();
TypeAdapterConfig<Source2, Destination2>.NewConfig();
TypeAdapterConfig.GlobalSettings.Compile();

7.2、编译配置

Mapster 默认将在第一次调用映射时自动编译:

var result = poco.Adapt<Dto>();

你也可以通过调用 配置实例 或 特定映射配置的Compile 方法编译映射:

// 全局配置实例
TypeAdapterConfig.GlobalSettings.Compile();

// 配置实例
var config = new TypeAdapterConfig();
config.Compile();

// 特定配置
var config = TypeAdapterConfig<Source, Destination>.NewConfig();
config.Compile();

推荐在程序添加映射配置完成后调用一次 Compile 方法,可以快速验证 映射配置中是否存在错误,而不是在运行到某一行业务代码时触发错误降低效率。

注意!调用 Compile 方法前应该完成所有的映射配置,调用 Compile 方法之后 配置实例 就不允许添加修改其它映射配置!

8、自定义映射

8.1、自定义属性映射关系

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .Map(dest => dest.FullName,
        src => string.Format("{0} {1}", src.FirstName, src.LastName));
TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .Map(dest => dest.Gender,      // dest.Gender: Genders.Male 或 Genders.Female 枚举类型
        src => src.GenderString); // src.GenderString: "Male" 或 "Female" 字符串类型

8.2、条件映射

可以通过设置 Map 方法的第三个参数,实现在 源对象 满足某些条件下进行映射;

当存在多个条件的情况下,Mapster 会依次往下执行判断条件是否满足,直到满足条件为止;

当找不到满足条件的映射时,将分配空值或默认值:

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .Map(dest => dest.FullName, src => "Sig. " + src.FullName, srcCond => srcCond.Country == "Italy")
    .Map(dest => dest.FullName, src => "Sr. " + src.FullName, srcCond => srcCond.Country == "Spain")
    .Map(dest => dest.FullName, src => "Mr. " + src.FullName);

 使用 IgnoreIf 方法,当满足条件时将忽略此成员的映射:

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .IgnoreIf((src, dest) => !string.IsNullOrEmpty(dest.Name), dest => dest.Name);

 Mapster 默认映射时会将 源对象的所有成员映射到目标对象,如果不想映射空值,那么可以使用 IgnoreNullValues 方法进行配置:

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .IgnoreNullValues(true);

 注意!如果想要在满足条件时跳过映射,应该使用 IgnoreIf,详情可参阅

8.3、映射私有成员

TypeAdapterConfig<TSource, TDestination>
    .NewConfig()
    .Map("PrivateDestName", "PrivateSrcName");

8.4、多级映射

TypeAdapterConfig<Poco, Dto>.NewConfig()
    .Map(dest => dest.Child.Name, src => src.Name);

 映射 Dto 中属性的值到 Poco

public class Poco
{
    public string Name { get; set; }
    public string Extra { get; set; }
}

public class Dto
{
    public string Name { get; set; }
    public SubDto SubDto { get; set; }
}
public class SubDto
{
    public string Extra { get; set; }
}

如果想将 Dto 中的所有属性和 Dto.SubDto 中的所有属性映射到 Poco,那么可以通过配置 dto.SubDto 映射到 Poco 来实现:

TypeAdapterConfig<Dto, Poco>.NewConfig()
    .Map(poco => poco, dto => dto.SubDto);

8.5、空值

如果 src.Childnull,那么映射到 dest.Name 的配置不会抛出 NullPointerException ,而是映射空值:

TypeAdapterConfig<Poco, Dto>.NewConfig()
    .Map(dest => dest.Name, src => src.Child.Name);

8.6、多个源映射到同一个类型

如果想将 Dto1Dto2两个类型映射到 Poco 类型,那么可以通过将 Dto1 Dto2 包装成一个 tuple,然后将 tuple.Item1tuple.Item2 映射到 Poco 来实现:

TypeAdapterConfig<(Dto1, Dto2), Poco>.NewConfig()
    .Map(dest => dest, src => src.Item1)
    .Map(dest => dest, src => src.Item2);

 8.7、特性

AdaptIgnore 特性

当一个属性有 [AdaptIgnore] 标记时,这个属性将不会被映射:

public class Product {
    public string Id { get; set; }
    public string Name { get; set; }

    [AdaptIgnore]
    public decimal Price { get; set; }
}

当一个成员有 [AdaptIgnore] 标记时,不管是 源到目标 还是 目标到源 的映射都将会忽略这个成员,可以使用 MemberSide 指定单方的忽略。

例如,只有 Product 当作 源映射时,Price 字段才会被忽略:

public class Product {
    public string Id { get; set; }
    public string Name { get; set; }

    [AdaptIgnore(MemberSide.Source)]
    public decimal Price { get; set; }
}

AdaptMember 特性标记

使用 [AdaptMember] 特性标记可以实现修改映射的名称。

例如,将 Id 映射为 Code:

public class Product {
    [AdaptMember("Code")]
    public string Id { get; set; }
    public string Name { get; set; }
}

使用 [AdaptMember] 特性标记可以实现映射非公开成员:

public class Product {
    [AdaptMember]
    private string HiddenId { get; set; }
    public string Name { get; set; }
}
  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

James.TCG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值