本文基於 AutoMapper 9.0.0數組
AutoMapper 是一個對象-對象映射器,能夠將一個對象映射到另外一個對象。app
官網地址:http://automapper.org/ide
官方文檔:https://docs.automapper.org/en/latest/函數
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); }
在使用 Map
方法以前,首先要告訴 AutoMapper 什麼類能夠映射到什麼類。ui
var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());
每一個 AppDomain 只能進行一次配置。這意味着放置配置代碼的最佳位置是在應用程序啓動中,例如 ASP.NET 應用程序的 Global.asax 文件。code
從 9.0 開始 Mapper.Initialize
方法就不可用了。對象
Profile
是組織映射的另外一種方式。新建一個類,繼承 Profile
,並在構造函數中配置映射。繼承
public class EmployeeProfile : Profile { public EmployeeProfile() { CreateMap<Employee, EmployeeDto>(); } } var config = new MapperConfiguration(cfg => { cfg.AddProfile<EmployeeProfile>(); });
Profile
內部的配置僅適用於 Profile
內部的映射。應用於根配置的配置適用於全部建立的映射。文檔
AutoMapper 也能夠在指定的程序集中掃描從 Profile
繼承的類,並將其添加到配置中。get
var config = new MapperConfiguration(cfg => { // 掃描當前程序集 cfg.AddMaps(System.AppDomain.CurrentDomain.GetAssemblies()); // 也能夠傳程序集名稱(dll 名稱) cfg.AddMaps("LibCoreTest"); });
默認狀況下,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); }
默認狀況下,AutoMapper 僅映射 public
成員,但其實它是能夠映射到 private
屬性的。
var config = new MapperConfiguration(cfg => { cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.SetMethod.IsPrivate; cfg.CreateMap<Source, Destination>(); });
須要注意的是,這裏屬性必須添加 private set
,省略 set
是不行的。
默認狀況下,AutoMapper 嘗試映射每一個公共屬性/字段。如下配置將忽略字段映射。
var config = new MapperConfiguration(cfg => { cfg.ShouldMapField = fi => false; });
var config = new MapperConfiguration(cfg => { cfg.RecognizePrefixes("My"); cfg.RecognizePostfixes("My"); }
var config = new MapperConfiguration(cfg => { cfg.ReplaceMemberName("Ä", "A"); });
這功能咱們基本上用不上。
有些類,屬性的 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());
禁用構造函數映射的話,目標類要有一個無參構造函數。
數組和列表的映射比較簡單,僅需配置元素類型,定義簡單類型以下:
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);
具體來講,支持的源集合類型包括:
映射到現有集合時,將首先清除目標集合。若是這不是你想要的,請查看AutoMapper.Collection。
映射集合屬性時,若是源值爲 null
,則 AutoMapper 會將目標字段映射爲空集合,而不是 null
。這與 Entity Framework 和 Framework Design Guidelines 的行爲一致,認爲 C# 引用,數組,List,Collection,Dictionary 和 IEnumerables 永遠不該該爲 null
。
這個官方的文檔不是很好理解。我從新舉個例子。實體類以下:
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
。
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; } }
當源類型與目標類型名稱不一致時,或者須要對源數據作一些轉換時,能夠用自定義映射。
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)); });
對象-對象映射的常見用法之一是將複雜的對象模型並將其展平爲更簡單的模型。
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
。
若是屬性命名不符合上述的規則,而是像下面這樣:
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>(); });
有時,咱們可能不須要展平。看以下例子:
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>(); });