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

相关文章