.NET模型映射器AutoMapper 9.0發布了,官方宣稱不再支持靜態方法調用了,老版本的部分API將在升級到9.0后,直接升級包到9.0會編譯報錯,所以寫篇文章記錄下AutoMapper新版本的學習過程吧,如果還不知道AutoMapper是什么的,建議先看這篇文章:https://masuit.com/156,或者參考官方文檔:https://automapper.readthedocs.io/en/latest/Getting-started.html
AutoMapper9.0快速上手
首先,我們准備兩個需要用於相互轉換的類:
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的映射轉換。
准備好實體模型后我們將AutoMapper9.0通過nuget安裝到項目中:
開始寫映射代碼吧,首先,我們需要明確源和目標類型。由於目標類型的設計一般會受其所在層的影響,比如通常情況下我們最終呈現在頁面上的數據結構和我們數據庫底層設計會有所不同,但只要成員的名稱與源類型的成員匹配,AutoMapper就能發揮最佳效果。不知何時開始的,AutoMapper可以實現自動映射了,也就是如果需要映射的源和目標的屬性和類型長得一樣,都可以不用寫映射配置了,比如源對象里面有一個名為“FirstName”的成員,則會自動將其映射到目標對象的名為“FirstName”成員上。
映射時,AutoMapper會忽略空引用異常。這是默認設計的。如果你覺得這樣做不好,你可以根據需要將AutoMapper的方法與自定義值解析器結合使用。
明確映射關系后,使用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)));
|
和早期版本一樣,左側的類型是源類型,右側的類型是目標類型。
現在,映射關系做好了,我們便可以創建映射器了:
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()));
|
從早期版本遷移至AutoMapper9.0
由於早期版本的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
; }
}
|
遷移點1:Mapper實例的創建
由於早期版本都是通過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));
}
}
|
遷移點2:ProjectTo的新實現
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的依賴注入擴展
AutoMapper官方還提供了一個nuget包,用於AutoMapper的依賴注入實現:AutoMapper.Extensions.Microsoft.Dependency
安裝擴展后,Startup.cs里面只需要一句話:
1
|
services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
即可實現Mapper實例的托管。
同樣,AutoMapper實例也可以被Autofac托管,實現屬性注入!
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融合為一體!🤣🤣🤣
上面都是.NET Core的代碼,.NET Framework怎么辦?
由於AutoMapper和autofac都是基於.NET Standard的項目,所以用法上都是大同小異,我相信你如果在項目中用了這兩個庫,要升級AutoMapper9.0也不難了,升級后哪些地方報錯的,就按上面的步驟弄吧。
完整Demo代碼下載
https://www.lanzous.com/i5qhrvg