前言
在專案中我們會經常遇到物件的對映,比如像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
對映到新物件
-
新建類
UserDto
public class UserDto { public int Id { get; set; } public string Name { get; set; } public uint Gender { get; set; } public DateTime BirthDay { get; set; } }
-
新建一個匿名物件,作為待轉換的物件源
var user = new { Id = 1, Name = "Tom", Gender = 1, BirthDay = DateTime.Parse("2002-01-01") };
-
將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的按需查詢,例如:
-
新建類
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);//使用記憶體資料庫,方便測試 } }
-
新建類
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; } }
-
使用基於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.Mapster
,Microsoft.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
-
新建類
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; } }
-
新建類
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); } }
-
修改類
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,歡迎聯絡我們