初步認識AutoMapperhtml
前言app
手動映射ide
使用AutoMapper工具
建立映射spa
Conventions插件
映射到一個已存在的實例對象code
一般在一個應用程序中,咱們開發人員會在兩個不一樣的類型對象之間傳輸數據,一般咱們會用DTOs(數據傳輸對象),View Models(視圖模型),或者直接是一些從一個service或者Web API的一些請求或應答對象。一個常見的須要使用數據傳輸對象的狀況是,咱們想把屬於一個對象的某些屬性值賦值給另外一個對象的某些屬性值,可是問題是,這個兩個對象可能並非徹底匹配的,好比,二者之間的屬性類型,名稱等等,是不同的,或者咱們只是想把一個對象的一部分屬性值賦值給另外一個對象。orm
首先,讓咱們來看下以前的處理方式,咱們經過如下這個例子來直觀感覺這種方式,咱們建立了如下三個類:htm
public class Author
對象
{
public string Name { get; set; }
}
public class Book
{
public string Title { get; set; }
public Author Author { get; set; }
}
public class BookViewModel
{
public string Title { get; set; }
public string Author { get; set; }
}
爲了建立Book對象實例的一個View Model對象實例-BookViewModel對象實例,咱們須要寫以下代碼:
BookViewModel model = new BookViewModel
{
Title = book.Title,
Author = book.Author.Name
}
上面的例子至關的直觀了,可是問題也隨之而來了,咱們能夠看到在上面的代碼中,若是一旦在Book對象裏添加了一個額外的字段,然後想在前臺頁面輸出這個字段,那麼就須要去在項目裏找到每一處有這樣轉換字段的地方,這是很是繁瑣的。另外,BookViewModel.Author是一個string類型的字段,可是Book.Author屬性倒是Author對象類型的,咱們用的解決方法是經過Book.Auther對象來取得Author的Name屬性值,而後再賦值給BookViewModel的Author屬性,這樣看起行的通,可是想想,若是打算在之後的開發中把Name拆分紅兩個-FisrtName和LastName,那麼,呵呵,咱們得去把原來的ViewModel對象也拆分紅對應的兩個字段,而後在項目中找到全部的轉換,而後替換。
那麼有什麼辦法或者工具來幫助咱們可以避免這樣的狀況發生呢?AutoMapper正是符合要求的一款插件。
到如今,確切的說,AutoMapper的安裝使用很是很是的便捷,就如同傻瓜照相機那樣。你只須要從Nuget上下載AutoMapper的包到你的應用程序裏,而後添加對AutoMapper命名空間的引用,而後你就能夠在你的項目裏隨意使用它了。如下就是一個很是簡單的是例子:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>();
var model = AutoMapper.Mapper.Map<BookViewModel>(book);
使用AutoMappeer的好處是顯而易見的,首先,再也不須要咱們去對DTO實例的屬性一一賦值,而後不管你在Book對象或者BookViewModel對象里加了一個或者更多的字段,那都不會影響這個段映射的代碼,我再也不須要去找到每一處轉換的地方去更改代碼,你的程序會像以前正常運轉。
不過,仍是有個問題並無獲得很好的解決,這也是在AutoMapper文檔上缺失的,爲把Book.Athor.Name字段賦值給BookViewModel.Author字段,須要在每一處須要執行映射的代碼地方,同時建立一個以下的顯示轉換申明代碼,因此若是有不少處轉換的話,那麼咱們就會寫不少重複的這幾行代碼:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));
因此咱們該如何正確的建立映射呢?方式有不少,我這邊說下在ASP.NET MVC的程序裏如何處理。
在微軟的ASP.NET MVC程序中,它提供了一個Global.asax文件,這個文件裏能夠放置一些全劇配置,上面對於把Book.Athor.Name字段賦值給BookViewModel.Author字段這個映射配置放置在這個文件裏面,那麼這段代碼只會跑一次可是全部轉換的地方都能正確的轉換Book.Athor.Name爲BookViewModel.Author。固然,Global.asax文件中不建議放很複雜的代碼,由於這是ASP.NET程序的入口,一檔這個文件裏出錯,那麼整個程序就會over。配置代碼能夠以這樣的形式寫,建立一個AutoMapper的配置類:
public static class AutoMapperConfig
{
public static void RegisterMappings()
{
AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));
}
}
而後再Global文件註冊這個類:
protected override void Application_Start(object sender, EventArgs e)
{
AutoMapperConfig.RegisterMappings();
}
全部的映射是有CreateMap方法來完成的:
AutoMapper.Mapper.CreateMap<SourceClass, >();
須要注意的是:這種方式是單向的匹配,即在在建立了上面的映射了以後咱們能夠在程序裏從一個SourceClass實例獲得一個DestinationClass類型的對象實例:
var destinationClass= AutoMapper.Mapper.Map<DestinationClass>(sourceClass);
可是若是嘗試從DestinationClass映射到一個SourceClass,咱們到的是一個錯誤信息:
var book = AutoMapper.Mapper.Map<Book>(bookViewModel);
幸運的是,AutoMapper已經考慮到這個問題了,它提供了ReverseMap方法:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>().ReverseMap();
使用了這個方式後你就能夠從Book建立BookViewModel,同時也能夠從BookViewModel建立Book對象實例。
AutoMapper之因此能和任何一種集合類型產生交集,是因爲它能夠配置各類Conventions來完成一個類型到另外一個類型的映射。最基本的一點就是兩個映射類型之間的字段名稱須要相同。例如一下的一個例子:
public class Book
{
public string Title { get; set; }
}
public class NiceBookViewModel
{
public string Title { get; set; }
}
public class BadBookViewModel
{
public string BookTitle { get; set; }
}
若是從Book映射到NiceBookViewModel,那麼NiceBookBiewModel的Title屬性會被正確設置,可是若是將Book映射爲BadBookViewModel,那麼BookTitle的屬性值將會爲NULL值。因此這種狀況下,AutoMapper看起來失效了,不過,幸運的是,AutoMapper已經預先考慮到這種狀況了,AutoMapper能夠經過投影的方式來正確的映射BadBookViewModel和Book,只須要一行代碼:
AutoMapper.Mapper.CreateMap<Book, BadBookViewModel>()
.ForMember(dest => dest.BookTitle,
opts => opts.MapFrom(src => src.Title));
一種比較複雜的狀況的是,當一個類型中引用了另外一個類型的做爲其一個屬性,例如:
public class Author
{
public string Name { get; set; }
}
public class Book
{
public string Title { get; set; }
public Author Author { get; set; }
}
public class BookViewModel
{
public string Title { get; set; }
public string Author { get; set; }
}
雖然Book和BookViewModel都有這一個Author的屬性子都,可是它們的類型是不一樣,全部若是使用AutoMapper來映射Book的Author到BookViewModel的Author,咱們獲得的仍是一個NULL值。對於這種以另外一個類型爲屬性的映射,AutoMapper內置默認的有個Conventions是會這個的屬性名加上這個屬性的類型裏的屬性名稱映射到目標類型具備相同名稱的字段,即若是在BookViewModel裏有一個叫AuthorName的,那麼咱們能夠獲得正確的Name值。可是若是咱們既不想更名稱,又想能正確的映射,怎麼辦呢?Convention就是爲此而誕生的:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(src => src.Author.Name));
對於AutoMapper,它提供的Conventions功能遠不止這些,對於更加複雜的情形,它也可以應對,例如當Author類型的字段有兩個屬性組成:
public class Author
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
可是咱們仍然只想映射到BookViewModel的一個字段,爲此,咱們能夠這麼作:
AutoMapper.Mapper.CreateMap<Book, BookViewModel>()
.ForMember(dest => dest.Author,
opts => opts.MapFrom(
src => string.Format("{0} {1}",
src.Author.FirstName,
src.Author.LastName)));
還能夠更加複雜,例如:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public class PersonDTO
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
若是從Person映射爲PersonDTO,咱們只要想上面同樣的作飯就能夠了。可是若是這個時候咱們要作的是把PersonDTO映射爲Book實體呢?代碼實際上是差很少的:
AutoMapper.Mapper.CreateMap<PersonDTO, Person>()
.ForMember(dest => dest.Address,
opts => opts.MapFrom(
src => new Address
{
Street = src.Street,
City = src.City,
State = src.State,
ZipCode = src.ZipCode
}));
因此,咱們在Convertion中構建了一個新的Address的實例,而後賦值給Book的Address的屬性。
有時候,咱們可能建立了不止一個DTO來接受映射的結果,例如,對於Address,咱們一樣建立了一個AddressDTO:
public class AddressDTO
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
public class PersonDTO
{
public string FirstName { get; set; }
public string LastName { get; set; }
public AddressDTO Address { get; set; }
}
這個時候若是咱們直接嘗試把Person映射爲PersonDTO,會報錯,映射AutoMapper並不知道Address和AddressDTO之間的映射關係,咱們須要手動建立:
AutoMapper.Mapper.CreateMap<PersonDTO, Person>();
AutoMapper.Mapper.CreateMap<AddressDTO, Address>();
以前咱們都是把映射獲得的結果賦值給一個變量,AutoMapper提供了另一種方式,它使得咱們能夠直接映射兩個已存在的實例。
以前的作法:
AutoMapper.Mapper.CreateMap<SourceClass, DestinationClass>();
var destinationObject = AutoMapper.Mapper.Map<DestinatationClass>(sourceObject);
直接映射的作法:
AutoMapper.Mapper.Map(sourceObject, destinationObject);
AutoMapper也支持映射集合對象:
var destinationList = AutoMapper.Mapper.Map<List<DestinationClass>>(sourceList);
對於ICollectionIEnumerable的也是一樣適用。可是在用AutoMapper來實現內部的集合映射的時候,是很是很是不愉快的,由於AutoMapper會把這個集合做爲一個屬性來映射賦值,而不是把內置的集合裏的一行行內容進行映射,例如對於以下的一個例子:
public class Pet
{
public string Name { get; set; }
public string Breed { get; set; }
}
public class Person
{
public List<Pet> Pets { get; set; }
}
public class PetDTO
{
public string Name { get; set; }
public string Breed { get; set; }
}
public class PersonDTO
{
public List<PetDTO> Pets { get; set; }
}
咱們在頁面上建立一個更新Pet類型的Name屬性的功能,而後提交更新,收到的數據差很少是這樣:
{
Pets: [
{ Name : "Sparky", Breed : null },
{ Name : "Felix", Breed : null },
{ Name : "Cujo", Breed : null }
]
}
這個時候若是咱們去將Person映射爲PersonDTO:
AutoMapper.Mapper.Map(person, personDTO);
咱們獲得將是一個全新的Pet的集合,即Name是更新後的數據,可是全部的Breed的值都將爲NULL,這個不是所指望的結果。
很不幸的是,AutoMapper並無提供很好的解決方案。目前能作的一種方案就是用AutoMapper的Ignore方法忽略Pet的屬性的映射,而後咱們本身去完成映射:
AutoMapper.Mapper.CreateMap<PersonDTO, Person>()
.ForMember(dest => dest.Pets,
opts => opts.Ignore());
AutoMapper.Mapper.Map(person, personDTO);
for (int i = 0; i < person.Pets.Count(); i++)
{
AutoMapper.Mapper.Map(person.Pets[i], personDTO.Pets[i]);
}
轉自:https://www.cnblogs.com/fred-bao/p/5700776.html