如何在.NET中实现对象数据映射

语言: CN / TW / HK

theme: cyanosis highlight: vs2015


  • 持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

前言

对象数据映射即将一个对象的数据根据特定规则批量映射到另一个对象中,减少手工操作和降低人为出错率。如将 DTO 对象和 Entity 实体相互转换映射。

示例

在我们平常表单提交中,我们通常会定义一个DTO让用户填写一些必须的信息而并不是将数据库所有的字段罗列让用户填写,在过去我们需要如何操作: // 数据库User表 public class User { public int UserId { get; set; } // 用户编号 public string UserName { get; set; } // 用户名称 public int Age { get; set; } // 年龄 public DateTime? CreateAt { get; set; } // 创建时间 public int CreateBy { get; set; } // 创建人 public DateTime Birthday { get; set; } // 生日 } 如上数据库表设计,我们用户编号、创建时间、创建人、包括年龄都是系统计算或者系统生成的,可能提供给用户填写的数据只有名称和生日: public class UserRequestDto { public string UserName { get; set; } public DateTime Birthday { get; set; } } 在以前我们应该这样处理 public async Task Create(UserRequestDto request) { // 实例化一个User实体,并且将用户填写内容一个一个赋值 User user = new User(); user.UserName = request.UserName; user.Birthday = request.Birthday; user.CreateAt = DateTime.Now; .... // 创建用户 await context.User.InsertAsync(user); }

问题: 如果很多地方需要这样的赋值操作,那么将有非常多的代码冗余,而且如果字段过多非常容易出错,操作效率极低。

有了如上问题,我们实现自动映射的需求就出现了,在C#中有比较优秀的对象映射工具 MapsterAutoMapper,据说 Mapster 使用简单且性能高。

Mapster 使用

Mapster 是一个使用简单,功能强大,性能极佳的对象映射框架。与 AutoMapper 相比在速度和内存占用方面表现更加优秀,可以在只使用1/3内存的情况下获得4倍的性能提升。

Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | | ------------------------- | --------- | --------- | --------- | ---------- | ----- | ----- | --------- | | 'Mapster 6.0.0' | 108.59 ms | 1.198 ms | 1.811 ms | 31000.0000 | - | - | 124.36 MB | | 'Mapster 6.0.0 (Roslyn)' | 38.45 ms | 0.494 ms | 0.830 ms | 31142.8571 | - | - | 124.36 MB | | 'Mapster 6.0.0 (FEC)' | 37.03 ms | 0.281 ms | 0.472 ms | 29642.8571 | - | - | 118.26 MB | | 'Mapster 6.0.0 (Codegen)' | 34.16 ms | 0.209 ms | 0.316 ms | 31133.3333 | - | - | 124.36 MB | | 'ExpressMapper 1.9.1' | 205.78 ms | 5.357 ms | 8.098 ms | 59000.0000 | - | - | 236.51 MB | | 'AutoMapper 10.0.0' | 420.97 ms | 23.266 ms | 35.174 ms | 87000.0000 | - | - | 350.95 MB

如上为官方提供的性能测试表格,当然还是根据个人喜好选择,具体测试结果也仅供参考,大家也可以自行研究选择。

image.png

  • 映射到一个新的对象 // 一行代码搞定,就是这么神奇 User user = request.Adapt<User>();

image.png

  • 在EFCore中使用 (Mapster 提供了对 IQueryable 的映射扩展)

在EFCore中查询所需要的格式我们通常使用Select实现 context.User.Select(x => new UserDto { UserName = x.UserName, Age = x.Age ... ... }) 使用 ProjectToType 映射到目标类型 var result = context.User.ProjectToType<UserDto>().ToList();

  • 自定义映射

在某些特殊情况下当我们源属性类型和目标属性名称不对应的时候我们可以进行自定义映射关系 // 在数据映射时,将出生日期通过计算方法映射给返回的Age TypeAdapterConfig<User, UserDto> .NewConfig() .Ignore("Id")//指定忽略某些字段 .Map(dest => dest.Age, src => CalcAge(src.Birthday));

  • 在某些情况下,如果需要在 依赖注入(DI)使用,Mapster提供了IMapperandMapper ``` public void ConfigureServices(IServiceCollection services) { var config = new TypeAdapterConfig(); services.AddSingleton(config);//使用单例注册 services.AddScoped();//注册服务 }

// Service进行依赖注入 private readonly IMapper _mapper; public UserService(IMapper mapper) { _mapper = mapper; }

public void Create(UserRequestDto request) { // 使用服务 var user = _mapper.Map(request); } - 数据类型转化 decimal i = 123.Adapt();// int转换成decimal

var e = "Read, Write, Delete".Adapt(); // 枚举 ```

总结

使用 Mapster 能让我们在处理尤其是 Entity 与 DTO 之间数据相互映射,如果手动映射会导致效率差,代码冗余, Mapster的优势还是非常明显的,当然也不是说 AutoMapper 就非常拉跨,大家根据自己的需求选择即可。