本文基於 AutoMapper 9.0.0 AutoMapper 是一個對象-對象映射器,可以將一個對象映射到另一個對象。 官網地址:http://automapper.org/ 官方文檔:https://docs.automapper.org/en/latest/ 1 入門例子 public cl ...
目錄
本文基於 AutoMapper 9.0.0
AutoMapper 是一個對象-對象映射器,可以將一個對象映射到另一個對象。
官方文檔:https://docs.automapper.org/en/latest/
1 入門例子
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
}
public class FooDto
{
public int ID { get; set; }
public string Name { get; set; }
}
public void Map()
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());
var mapper = config.CreateMapper();
Foo foo = new Foo { ID = 1, Name = "Tom" };
FooDto dto = mapper.Map<FooDto>(foo);
}
2 註冊
在使用 Map
方法之前,首先要告訴 AutoMapper 什麼類可以映射到什麼類。
var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());
每個 AppDomain 只能進行一次配置。這意味著放置配置代碼的最佳位置是在應用程式啟動中,例如 ASP.NET 應用程式的 Global.asax 文件。
從 9.0 開始 Mapper.Initialize
方法就不可用了。
2.1 Profile
Profile
是組織映射的另一種方式。新建一個類,繼承 Profile
,併在構造函數中配置映射。
public class EmployeeProfile : Profile
{
public EmployeeProfile()
{
CreateMap<Employee, EmployeeDto>();
}
}
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<EmployeeProfile>();
});
Profile
內部的配置僅適用於 Profile
內部的映射。應用於根配置的配置適用於所有創建的映射。
AutoMapper 也可以在指定的程式集中掃描從 Profile
繼承的類,並將其添加到配置中。
var config = new MapperConfiguration(cfg =>
{
// 掃描當前程式集
cfg.AddMaps(System.AppDomain.CurrentDomain.GetAssemblies());
// 也可以傳程式集名稱(dll 名稱)
cfg.AddMaps("LibCoreTest");
});
3 配置
3.1 命名約定
預設情況下,AutoMapper 基於相同的欄位名映射,並且是 不區分大小寫 的。
但有時,我們需要處理一些特殊的情況。
SourceMemberNamingConvention
表示源類型命名規則DestinationMemberNamingConvention
表示目標類型命名規則
LowerUnderscoreNamingConvention
和 PascalCaseNamingConvention
是 AutoMapper 提供的兩個命名規則。前者命名是小寫並包含下劃線,後者就是帕斯卡命名規則(每個單詞的首字母大寫)。
我的理解,如果源類型和目標類型分別採用了 蛇形命名法 和 駝峰命名法,那麼就需要指定命名規則,使其能正確映射。
public class Foo
{
public int Id { get; set; }
public string MyName { get; set; }
}
public class FooDto
{
public int ID { get; set; }
public string My_Name { get; set; }
}
public void Map()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Foo, FooDto>();
cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
});
var mapper = config.CreateMapper();
Foo foo = new Foo { Id = 2, MyName = "Tom" };
FooDto dto = mapper.Map<FooDto>(foo);
}
3.2 配置可見性
預設情況下,AutoMapper 僅映射 public
成員,但其實它是可以映射到 private
屬性的。
var config = new MapperConfiguration(cfg =>
{
cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.SetMethod.IsPrivate;
cfg.CreateMap<Source, Destination>();
});
需要註意的是,這裡屬性必須添加 private set
,省略 set
是不行的。
3.3 全局屬性/欄位過濾
預設情況下,AutoMapper 嘗試映射每個公共屬性/欄位。以下配置將忽略欄位映射。
var config = new MapperConfiguration(cfg =>
{
cfg.ShouldMapField = fi => false;
});
3.4 識別首碼和尾碼
var config = new MapperConfiguration(cfg =>
{
cfg.RecognizePrefixes("My");
cfg.RecognizePostfixes("My");
}
3.5 替換字元
var config = new MapperConfiguration(cfg =>
{
cfg.ReplaceMemberName("Ä", "A");
});
這功能我們基本上用不上。
4 調用構造函數
有些類,屬性的 set
方法是私有的。
public class Commodity
{
public string Name { get; set; }
public int Price { get; set; }
}
public class CommodityDto
{
public string Name { get; }
public int Price { get; }
public CommodityDto(string name, int price)
{
Name = name;
Price = price * 2;
}
}
AutoMapper 會自動找到相應的構造函數調用。如果在構造函數中對參數做一些改變的話,其改變會反應在映射結果中。如上例,映射後 Price
會乘 2。
禁用構造函數映射:
var config = new MapperConfiguration(cfg => cfg.DisableConstructorMapping());
禁用構造函數映射的話,目標類要有一個無參構造函數。
5 數組和列表映射
數組和列表的映射比較簡單,僅需配置元素類型,定義簡單類型如下:
public class Source
{
public int Value { get; set; }
}
public class Destination
{
public int Value { get; set; }
}
映射:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>();
});
IMapper mapper = config.CreateMapper();
var sources = new[]
{
new Source { Value = 5 },
new Source { Value = 6 },
new Source { Value = 7 }
};
IEnumerable<Destination> ienumerableDest = mapper.Map<Source[], IEnumerable<Destination>>(sources);
ICollection<Destination> icollectionDest = mapper.Map<Source[], ICollection<Destination>>(sources);
IList<Destination> ilistDest = mapper.Map<Source[], IList<Destination>>(sources);
List<Destination> listDest = mapper.Map<Source[], List<Destination>>(sources);
Destination[] arrayDest = mapper.Map<Source[], Destination[]>(sources);
具體來說,支持的源集合類型包括:
- IEnumerable
- IEnumerable
- ICollection
- ICollection
- IList
- IList
- List
- Arrays
映射到現有集合時,將首先清除目標集合。如果這不是你想要的,請查看AutoMapper.Collection。
5.1 處理空集合
映射集合屬性時,如果源值為 null
,則 AutoMapper 會將目標欄位映射為空集合,而不是 null
。這與 Entity Framework 和 Framework Design Guidelines 的行為一致,認為 C# 引用,數組,List,Collection,Dictionary 和 IEnumerables 永遠不應該為 null
。
5.2 集合中的多態
這個官方的文檔不是很好理解。我重新舉個例子。實體類如下:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Employee2 : Employee
{
public string DeptName { get; set; }
}
public class EmployeeDto
{
public int ID { get; set; }
public string Name { get; set; }
}
public class EmployeeDto2 : EmployeeDto
{
public string DeptName { get; set; }
}
數組映射代碼如下:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>().Include<Employee2, EmployeeDto2>();
cfg.CreateMap<Employee2, EmployeeDto2>();
});
IMapper mapper = config.CreateMapper();
var employees = new[]
{
new Employee { ID = 1, Name = "Tom" },
new Employee2 { ID = 2, Name = "Jerry", DeptName = "R & D" }
};
var dto = mapper.Map<Employee[], EmployeeDto[]>(employees);
可以看到,映射後,dto
中兩個元素的類型,一個是 EmployeeDto
,一個是 EmployeeDto2
,即實現了父類映射到父類,子類映射到子類。
如果去掉 Include
方法,則映射後 dto
中兩個元素的類型均為 EmployeeDto
。
6 方法到屬性映射
AutoMapper 不僅能實現屬性到屬性映射,還可以實現方法到屬性的映射,並且不需要任何配置,方法名可以和屬性名一致,也可以帶有 Get
首碼。
例如下例的 Employee.GetFullName()
方法,可以映射到 EmployeeDto.FullName
屬性。
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string GetFullName()
{
return $"{FirstName} {LastName}";
}
}
public class EmployeeDto
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get; set; }
}
7 自定義映射
當源類型與目標類型名稱不一致時,或者需要對源數據做一些轉換時,可以用自定義映射。
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime JoinTime { get; set; }
}
public class EmployeeDto
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public int JoinYear { get; set; }
}
如上例,ID
和 EmployeeID
屬性名不同,JoinTime
和 JoinYear
不僅屬性名不同,屬性類型也不同。
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>()
.ForMember("EmployeeID", opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.EmployeeName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.JoinYear, opt => opt.MapFrom(src => src.JoinTime.Year));
});
8 扁平化映射
對象-對象映射的常見用法之一是將複雜的對象模型並將其展平為更簡單的模型。
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
}
public class EmployeeDto
{
public int ID { get; set; }
public string Name { get; set; }
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
}
如果目標類型上的屬性,與源類型的屬性、方法都對應不上,則 AutoMapper 會將目標成員名按駝峰法拆解成單個單詞,再進行匹配。例如上例中,EmployeeDto.DepartmentID
就對應到了 Employee.Department.ID
。
8.1 IncludeMembers
如果屬性命名不符合上述的規則,而是像下麵這樣:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public Department Department { get; set; }
}
public class Department
{
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
}
public class EmployeeDto
{
public int ID { get; set; }
public string Name { get; set; }
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
}
Department
類中的屬性名,直接跟 EmployeeDto
類中的屬性名一致,則可以使用 IncludeMembers
方法指定。
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>().IncludeMembers(e => e.Department);
cfg.CreateMap<Department, EmployeeDto>();
});
9 嵌套映射
有時,我們可能不需要展平。看如下例子:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public string Heads { get; set; }
}
public class EmployeeDto
{
public int ID { get; set; }
public string Name { get; set; }
public DepartmentDto Department { get; set; }
}
public class DepartmentDto
{
public int ID { get; set; }
public string Name { get; set; }
}
我們要將 Employee
映射到 EmployeeDto
,並且將 Department
映射到 DepartmentDto
。
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Employee, EmployeeDto>();
cfg.CreateMap<Department, DepartmentDto>();
});