對象映射 – Mapping.Mapster

前言

在項目中我們會經常遇到對象的映射,比如像Model和Dto之間的映射,或者是對象的深拷貝,這些都是需要我們自己實現的。此時,項目中會出現很多初始化對象的代碼,這些代碼寫起來相當的枯燥乏味,那麼有沒有什麼辦法減輕我們的工作量,使得我們可以把時間花費到業務功能上呢?

目前,.Net中的對象映射框架,功能強大且性能極佳的對象映射框架已經存在,其中使用最多的有:

說到對象映射框架,大家想到的最多的是AutoMapper,可能很多人連Mapster都沒聽過,但不可否認的是Mapster確實是一個很好的對象映射框架,但由於中文文檔的缺失,導致在國內知名度不是很高,今天我們就來介紹一下Mapster提供了哪些功能,如何在項目中使用它,Masa提供的Mapster又做了什麼?

Mapster 簡介

Mapster是一個使用簡單,功能強大的對象映射框架,自2014年開源到現在已經過去8個年頭,截止到現在,github上已經擁有2.6k的star,並保持着每年3次的發版頻率,其功能與AutoMapper類似,提供對象到對象的映射、並支持IQueryable到對象的映射,與AutoMapper相比,在速度和內存佔用方面表現的更加優秀,可以在只使用1/3內存的情況下獲得4倍的性能提升,那我們下面就來看看Mapster如何使用?

準備工作

  • 新建一個控制台項目Assignment.Mapster,並安裝Mapster

    dotnet add package Mapster --version 7.3.0
    

映射到新對象

  1. 新建類UserDto

    public class UserDto
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    
        public uint Gender { get; set; }
    
        public DateTime BirthDay { get; set; }
    }
    
  2. 新建一個匿名對象,作為待轉換的對象源

    var user = new
    {
        Id = 1,
        Name = "Tom",
        Gender = 1,
        BirthDay = DateTime.Parse("2002-01-01")
    };
    
  3. 將user源對象映射到為目標對象 (UserDto)

    var userDto = user.Adapt<UserDto>();
    Console.WriteLine($"映射到新對象,Name: {userDto.Name}");
    

運行控制台程序驗證轉換成功:
映射到新對象

數據類型

除了提供對象到對象的映射,還支持數據類型的轉換,如:

基本類型

  • 提供類型映射的功能,類似Convert.ChangeType()

    string res = "123";
    decimal i = res.Adapt<decimal>(); //equal to (decimal)123;
    Console.WriteLine($"結果為:{i == int.Parse(res)}");
    

運行控制台程序: 基本類型轉換

枚舉類型

  • 把枚舉映射到數字類型,同樣也支持字符串到枚舉和枚舉到字符串的映射,比.NET的默認實現快兩倍

    var fileMode = "Create, Open".Adapt<FileMode>();//等於 FileMode.Create | FileMode.Open
    Console.WriteLine($"枚舉類型轉換的結果為:{fileMode == (FileMode.Create | FileMode.Open)}");
    

運行控制台程序驗證轉換成功:
基本類型轉換

Queryable擴展

Mapster提供了Queryable的擴展,用於實現DbContext的按需查找,例如:

  1. 新建類UserDbContext

    using Assignment.Mapster.Domain;
    using Microsoft.EntityFrameworkCore;
    
    namespace Assignment.Mapster.Infrastructure;
    
    public class UserDbContext : DbContext
    {
        public DbSet<User> User { get; set; }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var dataBaseName = Guid.NewGuid().ToString();
            optionsBuilder.UseInMemoryDatabase(dataBaseName);//使用內存數據庫,方便測試
        }
    }
    
  2. 新建類User

    public class User
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    
        public uint Gender { get; set; }
    
        public DateTime BirthDay { get; set; }
    
        public DateTime CreationTime { get; set; }
    
        public User()
        {
            CreationTime = DateTime.Now;
        }
    }
    
  3. 使用基於Queryable的擴展方法ProjectToType

    using (var dbContext = new UserDbContext())
    {
        dbContext.Database.EnsureCreated();
    
        dbContext.User.Add(new User()
        {
            Id = 1,
            Name = "Tom",
            Gender = 1,
            BirthDay = DateTime.Parse("2002-01-01")
        });
        dbContext.SaveChanges();
    
        var userItemList = dbContext.User.ProjectToType<UserDto>().ToList();
    }
    

運行控制台程序驗證轉換成功:
Queryable擴展

除此之外,Mapster還提供了映射前/後處理,拷貝與合併以及映射配置嵌套支持,詳細可查看文檔,既然Mapster已經如此強大,那我直接使用它就可以了,為什麼還要使用Masa提供的Mapper呢?

什麼是Masa.Contrib.Data.Mapping.Mapster?

Masa.Contrib.Data.Mapping.Mapster是基於Mapster的一個對象到對象的映射器,並在原來Mapster的基礎上增加自動獲取並使用最佳構造函數映射,支持嵌套映射,減輕映射的工作量。

映射規則

  • 目標對象沒有構造函數時:使用空構造函數,映射到字段和屬性。

  • 目標對象存在多個構造函數:獲取最佳構造函數映射

    最佳構造函數: 目標對象構造函數參數數量從大到小降序查找,參數名稱一致(不區分大小寫)且參數類型與源對象屬性一致

準備工作

  • 新建一個控制台項目Assignment.Masa.Mapster,並安裝Masa.Contrib.Data.Mapping.MapsterMicrosoft.Extensions.DependencyInjection

    dotnet add package Masa.Contrib.Data.Mapping.Mapster --version 0.4.0-rc.4
    dotnet add package Microsoft.Extensions.DependencyInjection --version 6.0.0
    
  1. 新建類OrderItem

    public class OrderItem
    {
        public string Name { get; set; }
    
        public decimal Price { get; set; }
    
        public int Number { get; set; }
    
        public OrderItem(string name, decimal price) : this(name, price, 1)
        {
    
        }
    
        public OrderItem(string name, decimal price, int number)
        {
            Name = name;
            Price = price;
            Number = number;
        }
    }
    
  2. 新建類Order

    public class Order
    {
        public string Name { get; set; }
    
        public decimal TotalPrice { get; set; }
    
        public List<OrderItem> OrderItems { get; set; }
    
        public Order(string name)
        {
            Name = name;
        }
    
        public Order(string name, OrderItem orderItem) : this(name)
        {
            OrderItems = new List<OrderItem> { orderItem };
            TotalPrice = OrderItems.Sum(item => item.Price * item.Number);
        }
    }
    
  3. 修改類Program

    using Assignment.Masa.Mapster.Domain.Aggregate;
    using Masa.BuildingBlocks.Data.Mapping;
    using Masa.Contrib.Data.Mapping.Mapster;
    using Microsoft.Extensions.DependencyInjection;
    
    Console.WriteLine("Hello Masa Mapster!");
    
    IServiceCollection services = new ServiceCollection();
    services.AddMapping();
    
    var request = new
    {
        Name = "Teach you to learn Dapr ……",
        OrderItem = new OrderItem("Teach you to learn Dapr hand by hand", 49.9m)
    };
    var serviceProvider = services.BuildServiceProvider();
    var mapper = serviceProvider.GetRequiredService<IMapper>();
    var order = mapper.Map<Order>(request);
    
    Console.WriteLine($"{nameof(Order.TotalPrice)} is {order.TotalPrice}");//控制台輸出49.9
    
    Console.ReadKey();
    

如果轉換成功,TotalPrice的值應該是49.9,那麼我們運行控制台程序來驗證轉換是否成功:

Mapping.Mapster

如何實現

上面我們提到了Masa.Contrib.Data.Mapping.Mapster可以自動獲取並使用最佳構造函數映射,進而完成對象到對象的映射,那麼它是如何實現的呢?會不會對性能有什麼影響呢?

做到自動獲取並使用最佳構造函數映射是使用的Mapster提供的構造函數映射的功能,通過指定構造函數,完成對象到對象的映射。

查看文檔

總結

目前Masa.Contrib.Data.Mapping.Mapster的功能相對較弱,當前版本與Mapster的相比僅僅增加了一個自動獲取並使用最佳構造函數的功能,讓我們在面對無空構造函數且擁有多個構造函數的類時也能輕鬆的完成映射,不需要額外多寫一行代碼。

但我覺得Masa版的Mapping最大的好處是項目依賴的是BuildingBlocks下的IMapper,而不是Mapster,這也就使得我們的項目與具體的映射器實現脫離,如果我們被要求項目必須要使用AutoMapper,只需要實現AutoMapper版的IMapper即可,無需更改太多的業務代碼,僅需要更換一下引用的包即可,這也是BuildingBlocks的魅力所在

本章源碼

Assignment04

//github.com/zhenlei520/MasaFramework.Practice

開源地址

MASA.BuildingBlocks://github.com/masastack/MASA.BuildingBlocks

MASA.Contrib://github.com/masastack/MASA.Contrib

MASA.Utils://github.com/masastack/MASA.Utils

MASA.EShop://github.com/masalabs/MASA.EShop

MASA.Blazor://github.com/BlazorComponent/MASA.Blazor

如果你對我們的 MASA Framework 感興趣,無論是代碼貢獻、使用、提 Issue,歡迎聯繫我們

16373211753064.png