.NET模型映射器AutoMapper 9.0發佈了,官方宣稱再也不支持靜態方法調用了,老版本的部分API將在升級到9.0後,直接升級包到9.0會編譯報錯,因此寫篇文章記錄下AutoMapper新版本的學習過程吧,若是還不知道AutoMapper是什麼的,建議先看這篇文章:https://masuit.com/156,或者參考官方文檔:https://automapper.readthedocs.io/en/latest/Getting-started.htmlhtml
首先,咱們準備兩個須要用於相互轉換的類:git
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
Person
{
public
string
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
Address Address {
get
;
set
; }
}
public
class
Address
{
public
string
Province {
get
;
set
; }
public
string
City {
get
;
set
; }
public
string
Street {
get
;
set
; }
}
|
1
2
3
4
5
6
|
public
class
PersonDto
{
public
string
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
string
Address {
get
;
set
; }
}
|
咱們想實現從Person到PersonDto的映射轉換。web
準備好實體模型後咱們將AutoMapper9.0經過nuget安裝到項目中:數據庫
開始寫映射代碼吧,首先,咱們須要明確源和目標類型。因爲目標類型的設計通常會受其所在層的影響,好比一般狀況下咱們最終呈如今頁面上的數據結構和咱們數據庫底層設計會有所不一樣,但只要成員的名稱與源類型的成員匹配,AutoMapper就能發揮最佳效果。不知什麼時候開始的,AutoMapper能夠實現自動映射了,也就是若是須要映射的源和目標的屬性和類型長得同樣,均可以不用寫映射配置了,好比源對象裏面有一個名爲「FirstName」的成員,則會自動將其映射到目標對象的名爲「FirstName」成員上。c#
映射時,AutoMapper會忽略空引用異常。這是默認設計的。若是你以爲這樣作很差,你能夠根據須要將AutoMapper的方法與自定義值解析器結合使用。api
明確映射關係後,使用MapperConfiguration和CreateMap 爲兩種類型建立映射關係。MapperConfiguration每一個AppDomain一般只須要一個實例,而且應該在應用程序啓動期間進行實例化。數據結構
1
|
var config =
new
MapperConfiguration(cfg => cfg.CreateMap<Person, PersonDto>().ForMember(p => p.Address, e => e.MapFrom(p => p.Address.Province + p.Address.City + p.Address.Street)));
|
和早期版本同樣,左側的類型是源類型,右側的類型是目標類型。app
如今,映射關係作好了,咱們即可以建立映射器了:ide
1
|
var mapper = config.CreateMapper();
|
這樣,即可以像往常同樣,使用Map方法進行對象的映射了,而現在大多數應用程序都在用依賴注入,因此AutoMapper如今也推薦咱們經過依賴注入來注入建立的IMapper實例。函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static
void
Main(
string
[] args)
{
var config =
new
MapperConfiguration(cfg => cfg.CreateMap<Person, PersonDto>().ForMember(p => p.Address, e => e.MapFrom(p => p.Address.Province + p.Address.City + p.Address.Street)));
var mapper = config.CreateMapper();
var person =
new
Person()
{
Id =
"1"
,
Name =
"李扯火"
,
Address =
new
Address()
{
Province =
"新日暮裏"
,
City =
"地牢"
,
Street =
"van先生的家"
}
};
var personDto = mapper.Map<PersonDto>(person);
}
|
一般狀況下,咱們爲了代碼規範,會將映射配置單獨寫在一個類中,而AutoMapper也爲咱們提供了這樣的一個「接口」,咱們只須要建立一個class,繼承自Profile,在構造函數中寫映射配置:
1
2
3
4
5
6
7
|
public
class
MappingProfile : Profile
{
public
MappingProfile()
{
CreateMap<Person, PersonDto>().ForMember(p => p.Address, e => e.MapFrom(p => p.Address.Province + p.Address.City + p.Address.Street));
}
}
|
1
|
var config =
new
MapperConfiguration(cfg => cfg.AddProfile(
new
MappingProfile()));
|
因爲早期版本的AutoMapper映射時咱們都直接調靜態方法Mapper.Map就能夠了,很爽,可是,9.0版本開始,取消了靜態方法的調用,這就意味着升級後的代碼可能須要隨處建立Mapper的實例或者使用依賴注入容器對AutoMapper的實例進行託管。
咱們先建立一個.NET Core的web項目,並建立一個基於內存的DbContext:
Models:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
Person
{
public
string
Id {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
Address Address {
get
;
set
; }
}
public
class
Address
{
public
string
Id {
get
;
set
; }
public
string
Province {
get
;
set
; }
public
string
City {
get
;
set
; }
public
string
Street {
get
;
set
; }
public
string
PersonId {
get
;
set
; }
public
Person Person {
get
;
set
; }
}
|
DataContext:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
DataContext : DbContext
{
public
DataContext(DbContextOptions<DataContext> options) :
base
(options)
{
}
protected
override
void
OnModelCreating(ModelBuilder modelBuilder)
{
base
.OnModelCreating(modelBuilder);
modelBuilder.Entity<Person>().HasOne(e => e.Address).WithOne(a => a.Person).IsRequired(
false
).OnDelete(DeleteBehavior.Cascade);
}
public
DbSet<Person> Persons {
get
;
set
; }
public
DbSet<Address> Address {
get
;
set
; }
}
|
因爲早期版本都是經過Mapper.Map靜態方法實現對象映射,如今須要一個Mapper實例了,因此,咱們就使用無處不在的依賴注入來管理它吧,咱們在Startup.cs裏面:
1
2
3
|
var config =
new
MapperConfiguration(e => e.AddProfile(
new
MappingProfile()));
var mapper = config.CreateMapper();
services.AddSingleton(mapper);
|
這樣即可以在須要用到Mapper的地方經過構造函數注入Mapper實例對象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
[Route(
"api/[controller]"
)]
[ApiController]
public
class
ValuesController : ControllerBase
{
private
readonly
IMapper _mapper;
private
readonly
DataContext _dataContext;
public
ValuesController(IMapper mapper, DataContext dataContext)
{
_mapper = mapper;
_dataContext = dataContext;
}
[HttpGet]
public
ActionResult Get()
{
var person = _dataContext.Persons.Include(p => p.Address).FirstOrDefault();
return
Ok(_mapper.Map<PersonDto>(person));
}
}
|
AutoMapper9.0的ProjectTo方法須要傳入一個MapperConfiguration對象,因此,要調用ProjectTo方法,還須要注入MapperConfiguration對象:
1
2
|
var config =
new
MapperConfiguration(e => e.AddProfile(
new
MappingProfile()));
services.AddSingleton(config);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
[Route(
"api/[controller]"
)]
[ApiController]
public
class
ValuesController : ControllerBase
{
private
readonly
IMapper _mapper;
private
readonly
MapperConfiguration _mapperConfig;
private
readonly
DataContext _dataContext;
public
ValuesController(IMapper mapper, DataContext dataContext, MapperConfiguration mapperConfig)
{
_mapper = mapper;
_dataContext = dataContext;
_mapperConfig = mapperConfig;
}
[HttpGet]
public
ActionResult Get()
{
var person = _dataContext.Persons.Include(p => p.Address).FirstOrDefault();
return
Ok(_mapper.Map<PersonDto>(person));
}
[HttpGet(
"list"
)]
public
ActionResult Gets()
{
var list = _dataContext.Persons.Include(p => p.Address).ProjectTo<PersonDto>(_mapperConfig).ToList();
return
Ok(list);
}
}
|
AutoMapper官方還提供了一個nuget包,用於AutoMapper的依賴注入實現:AutoMapper.Extensions.Microsoft.Dependency
安裝擴展後,Startup.cs裏面只須要一句話:
1
|
services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
便可實現Mapper實例的託管。
一樣,AutoMapper實例也能夠被Autofac託管,實現屬性注入!
Autofac的好處就在於它能批量注入和屬性注入,固然在這裏體現的就是autofac屬性注入的優點,省去了構造函數注入的麻煩,若是沒裝Resharper的同窗有時還會忘記注入,而autofac則解決了這樣的問題。
咱們新建一個.NET Core的web項目,並安裝好AutoMapper.Extensions.Microsoft.DependencyInjec和Autofac.Extensions.DependencyInjection這兩個nuget包,由於這兩個包已經包含了AutoMapper和autofac,因此不須要單獨安裝這兩個包。
準備一個Service來模擬咱們的項目分層:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
interface
IPersonService
{
List<PersonDto> GetAll();
Person Get(
string
id);
}
public
class
PersonService : IPersonService
{
public
DataContext DataContext {
get
;
set
; }
public
MapperConfiguration MapperConfig {
get
;
set
; }
public
List<PersonDto> GetAll()
{
return
DataContext.Persons.ProjectTo<PersonDto>(MapperConfig).ToList();
}
public
Person Get(
string
id)
{
return
DataContext.Persons.FirstOrDefault(p => p.Id == id);
}
}
|
注意上面的代碼,沒有寫構造函數。
而後咱們改造Startup.cs,讓依賴注入容器使用Autofac託管:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataContext>(opt => opt.UseInMemoryDatabase());
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddControllersAsServices().AddViewComponentsAsServices().AddTagHelpersAsServices();
var config =
new
MapperConfiguration(e => e.AddProfile(
new
MappingProfile()));
services.AddSingleton(config);
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddAutofac();
ContainerBuilder builder =
new
ContainerBuilder();
builder.Populate(services);
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces().Where(t => t.Name.EndsWith(
"Service"
) || t.Name.EndsWith(
"Controller"
)).PropertiesAutowired().AsSelf().InstancePerDependency();
//註冊控制器爲屬性注入
var autofacContainer =
new
AutofacServiceProvider(builder.Build());
return
autofacContainer;
}
|
在控制器中,也能屬性注入,是否是不上面遷移時的代碼簡單了許多!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
[Route(
"api/[controller]"
)]
[ApiController]
public
class
ValuesController : ControllerBase
{
public
Mapper Mapper {
get
;
set
; }
public
IPersonService PersonService {
get
;
set
; }
[HttpGet]
public
ActionResult Get()
{
return
Ok(Mapper.Map<PersonDto>(PersonService.Get(
"1"
)));
}
[HttpGet(
"list"
)]
public
ActionResult Gets()
{
var list = PersonService.GetAll();
return
Ok(list);
}
}
|
沒想到如此簡單,就將AutoMapper和Autofac融合爲一體!🤣🤣🤣
因爲AutoMapper和autofac都是基於.NET Standard的項目,因此用法上都是大同小異,我相信你若是在項目中用了這兩個庫,要升級AutoMapper9.0也不難了,升級後哪些地方報錯的,就按上面的步驟弄吧。
https://www.lanzous.com/i5qhrvg