AutoMapper 9.0快速上手,从老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合

要须心地收汗马,孔孟行世目杲杲。这篇文章主要讲述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 9.0快速上手,从老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合

文章图片

开始写映射代码吧,首先,我们需要明确源和目标类型。由于目标类型的设计一般会受其所在层的影响,比如通常情况下我们最终呈现在页面上的数据结构和我们数据库底层设计会有所不同,但只要成员的名称与源类型的成员匹配,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));                 }         }
AutoMapper 9.0快速上手,从老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合

文章图片

  迁移点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
AutoMapper 9.0快速上手,从老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合

文章图片

安装扩展后,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
 
【AutoMapper 9.0快速上手,从老版本迁移到9.0+AutoMapper9.0和Autofac的完美结合】出处:https://masuit.com/1625/ocrel

    推荐阅读