前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >C# 对象映射框架(Mapster & mapperly)

C# 对象映射框架(Mapster & mapperly)

作者头像
jgrass
发布2025-03-03 09:09:21
发布2025-03-03 09:09:21
7000
代码可运行
举报
文章被收录于专栏:蔻丁杂记
运行总次数:0
代码可运行

Mapster

可能是 .NET 领域性能最好的对象映射框架 —— Mapster - 明志唯新

MapsterMapper/Mapster: A fast, fun and stimulating object to object Mapper

代码语言:javascript
代码运行次数:0
复制
// 自定义转换配置public class MyMappingConfig{    public static void ConfigureMappings()    {        // UserViewObject 到 UserEntry 的映射配置        TypeAdapterConfig<UserViewObject, UserEntry>            .NewConfig()            .Map(dest => dest.Gender, src => (int)src.UserGender) // Gender 枚举转换为 int            .Map(dest => dest.Birthday, src => src.Birthday.ToString("yyyy-MM-dd")) // DateTime 转换为字符串            .Map(dest => dest.Address, src => src.HomeAddress); // HomeAddress 映射到 Address
        // UserEntry 到 UserViewObject 的映射配置        TypeAdapterConfig<UserEntry, UserViewObject>            .NewConfig()            .Map(dest => dest.UserGender, src => (Gender)src.Gender) // int 转换回 Gender 枚举            .Map(dest => dest.Birthday, src => DateTime.Parse(src.Birthday)); // 字符串转换为 DateTime    }}
public class AutoMapTest{    public static void DoTest()    {        // 创建示例 UserViewObject 对象        UserViewObject viewObject = new UserViewObject        {            Id = "1",            Name = "张三",            UserGender = Gender.Male,            Birthday = new DateTime(1990, 1, 1),            HomeAddress = "北京市",            Remark = "这是一个备注",        };
        // 将 UserViewObject 转换为 UserEntry        UserEntry entry = viewObject.Adapt<UserEntry>();        Console.WriteLine(            $"UserEntry: {entry.Id}, {entry.Name}, {entry.Gender}, {entry.Birthday}, {entry.Address}"        );
        // 创建示例 UserEntry 对象        UserEntry newEntry = new UserEntry        {            Id = "2",            Name = "李四",            Gender = 1, // 男性            Birthday = "1995-05-05",            CreateTime = DateTime.Now,            UpdateTime = DateTime.Now,            Address = "上海市",        };
        // 将 UserEntry 转换为 UserViewObject        UserViewObject newViewObject = newEntry.Adapt<UserViewObject>();        Console.WriteLine(            $"UserViewObject: {newViewObject.Id}, {newViewObject.Name}, {newViewObject.UserGender}, {newViewObject.Birthday}, {newViewObject.HomeAddress}"        );    }}
public enum Gender{    Unknown,    Male,    Female,}
public class UserViewObject{    public string Id { get; set; }
    public string Name { get; set; }
    public Gender UserGender { get; set; }
    public DateTime Birthday { get; set; }
    public string HomeAddress { get; set; }
    public string Remark { get; set; }}
public class UserEntry{    public string Id { get; set; }
    public string Name { get; set; }
    public int Gender { get; set; }
    public string Birthday { get; set; }
    public DateTime CreateTime { get; set; }
    public DateTime UpdateTime { get; set; }
    public string Address { get; set; }}

Mapster 和 AutoMapper 等“传统”映射框架的问题

自动的转换,让你失去了对变化的感知和追踪,比如有天某个 Model 的名称修改了,然后忘记了在转换配置中进行自定义处理,就可能留有隐患。

程序员千万不要用AutoMapper或者Mapster_哔哩哔哩_bilibili

我比较推荐的是使用源码生成的方式,并且充分控制转换方式。Mapster 有 CodeGen 的配置方式,但是感觉有点复杂了。真正的源码生成形式,还在路上。

Source Generators with IMapper Interface · Issue #622 · MapsterMapper/Mapster

发现 mapperly 可以符合需求

mapperly

riok/mapperly: A .NET source generator for generating object mappings. No runtime reflection.

Introduction | Mapperly

代码语言:javascript
代码运行次数:0
复制
public class AutoMapTest{    public static void DoTest()    {        // 创建示例 UserViewObject 对象        UserViewObject viewObject = new UserViewObject        {            Id = "1",            Name = "张三",            UserGender = Gender.Male,            Birthday = new DateTime(1990, 1, 1),            HomeAddress = "北京市",            Remark = "这是一个备注",        };
        // 将 UserViewObject 转换为 UserEntry        UserEntry entry = UserViewObjectMapper.ToUserEntry(viewObject);
        Console.WriteLine(            $"UserEntry: {entry.Id}, {entry.Name}, {entry.Gender}, {entry.Birthday}, {entry.Address}"        );
        // 创建示例 UserEntry 对象        UserEntry newEntry = new UserEntry        {            Id = "2",            Name = "李四",            Gender = 1, // 男性            Birthday = "1995-05-05",            CreateTime = DateTime.Now,            UpdateTime = DateTime.Now,            Address = "上海市",        };
        // 将 UserEntry 转换为 UserViewObject        UserViewObject newViewObject = UserViewObjectMapper.ToUserViewObject(newEntry);        Console.WriteLine(            $"UserViewObject: {newViewObject.Id}, {newViewObject.Name}, {newViewObject.UserGender}, {newViewObject.Birthday}, {newViewObject.HomeAddress}"        );    }}
[Riok.Mapperly.Abstractions.Mapper(    UseDeepCloning = true,    AutoUserMappings = false,    ThrowOnMappingNullMismatch = true,    ThrowOnPropertyMappingNullMismatch = true,    EnabledConversions = MappingConversionType.ExplicitCast | MappingConversionType.ImplicitCast)]public partial class UserViewObjectMapper{    [MapProperty(nameof(UserViewObject.HomeAddress), nameof(UserEntry.Address))] // Map property with a different name in the target type    [MapProperty(        nameof(UserViewObject.UserGender),        nameof(UserEntry.Gender),        Use = nameof(ToIntegerGender)    )]    [MapProperty(        nameof(UserViewObject.Birthday),        nameof(UserEntry.Birthday),        Use = nameof(ToBirthdayString)    )]    [MapperIgnoreSource(nameof(UserViewObject.Remark))]    [MapperIgnoreTarget(nameof(UserEntry.CreateTime))]    [MapperIgnoreTarget(nameof(UserEntry.UpdateTime))]    public static partial UserEntry ToUserEntry(UserViewObject vo);
    [MapProperty(nameof(UserEntry.Address), nameof(UserViewObject.HomeAddress))]    [MapProperty(        nameof(UserEntry.Gender),        nameof(UserViewObject.UserGender),        Use = nameof(ToGender)    )]    [MapProperty(        nameof(UserEntry.Birthday),        nameof(UserViewObject.Birthday),        Use = nameof(ToBirthdayDatetime)    )]    [MapperIgnoreSource(nameof(UserEntry.CreateTime))]    [MapperIgnoreSource(nameof(UserEntry.UpdateTime))]    [MapperIgnoreTarget(nameof(UserViewObject.Remark))]    public static partial UserViewObject ToUserViewObject(UserEntry entry);
    private static int ToIntegerGender(Gender gender)    {        return (int)gender;    }
    private static Gender ToGender(int gender)    {        return gender switch        {            0 => Gender.Unknown,            1 => Gender.Male,            2 => Gender.Female,            _ => throw new ArgumentOutOfRangeException(nameof(gender)),        };    }
    private static string ToBirthdayString(DateTime birthday)    {        return birthday.ToString("yyyy-MM-dd");    }
    private static DateTime ToBirthdayDatetime(string date)    {        return DateTime.Parse(date);    }}
代码语言:javascript
代码运行次数:0
复制
<WarningsAsErrors>RMG012;RMG020</WarningsAsErrors>

这里有两个重要的配置,

1 EnabledConversions = MappingConversionType.ExplicitCast | MappingConversionType.ImplicitCast

这个配置是说,处理明确存在显式转换和隐式转换,其它的都不进行自动处理。 比如 Datetime 和 string 之间,如果没有手动设置,就会按照默认的方式进行相互转换。

EnabledConversions 设置为仅支持 ExplicitCast 和 ImplicitCast 之后,则不会存在这种“偷偷摸摸”的默认操作。

2 在 csproj 中,将如下两个警告,设置成 Error。

可能还有其它的警告也最好设置成 Error。

代码语言:javascript
代码运行次数:0
复制
<WarningsAsErrors>RMG012;RMG020</WarningsAsErrors>

这样做的好处是,对于任何两个 Model 之间的差异,都必须手动处理,或者手动声明不处理,不会存在任何的模糊空间。

设想如下场景,本来两个 Model 中,属性名称都是 Name, 类型也都是 string, 则默认转换会处理得很好。 如果哪天其中一个重命名成了 FullName, 则会有上述警告,告知有转换操作没有进行。 但是,警告是容易被忽略的,一不下心就留下了 BUG 隐患。

如果是 Error, 则在修改属性命名之后,编译会无法通过,强力地提示此处存在问题。

总结

在 C# 中做对象映射时,推荐使用 mapperly 这类使用源码生成器来处理转换的工具库。 并结合配置,将其设置为对于任何差异都需要手动确认的配置形式,并使用编译时错误来进行约束。

原文链接: https://blog.jgrass.cc/posts/csharp-model-mapper/

本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025年3月1日 |,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Mapster
  • Mapster 和 AutoMapper 等“传统”映射框架的问题
  • mapperly
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档