AutoMapper 9.0快速上手,從老版本遷移到9.0+AutoMapper9.0和Autofac的完美結合


.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.DependencyInjecAutofac.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

 

出處:https://masuit.com/1625/ocrel


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM