物件對映

2022-06-30 12:02:58

前言

在專案中我們會經常遇到物件的對映,比如像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();
    }
    

執行控制檯程式驗證轉換成功:

除此之外,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,那麼我們執行控制檯程式來驗證轉換是否成功:

如何實現

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

做到自動獲取並使用最佳建構函式對映是使用的Mapster提供的建構函式對映的功能,通過指定建構函式,完成物件到物件的對映。

檢視檔案

總結

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

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

本章原始碼

Assignment04

https://github.com/zhenlei520/MasaFramework.Practice

開源地址

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

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

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

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

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

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