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

 

AutoMapper9.0快速上手

首先,咱们准备两个须要用于相互转换的类: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()));
 

从早期版本迁移至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