怎样优雅地增删查改(四):创建通用查询基类

上一章我们实现了Employee管理模块,Employee的增删改查是通过其应用服务类,继承自Abp.Application.Services.CrudAppService实现的。

我们将封装通用的应用层,接口以及控制器基类。

创建通用查询抽象层

创建接口ICurdAppService,在这里我们定义了通用的增删改查接口。

其中的泛型参数:

  • TGetOutputDto: Get方法返回的实体Dto
  • TGetListOutputDto: GetAll方法返回的实体Dto
  • TKey: 实体的主键
  • TGetListInput: GetAll方法接收的输入参数
  • TCreateInput: Create方法接收的输入参数
  • TUpdateInput: Update方法接收的输入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>

{
Task<TGetOutputDto> GetAsync(TKey id);

Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input);

Task<TGetOutputDto> CreateAsync(TCreateInput input);

Task<TGetOutputDto> UpdateAsync(TUpdateInput input);

Task DeleteAsync(TKey id);
}

创建通用查询应用层基类

创建应用层服务CurdAppServiceBase,它是一个抽象类,继承自Abp.Application.Services.CrudAppService。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput>
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}
}

创建通用查询控制器基类

创建控制器类CurdController,继承自AbpControllerBase。并实现ICurdAppService接口。

代码如下:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto,  TKey, TGetListInput,  TCreateInput, TUpdateInput>
: AbpControllerBase
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{

private readonly ITAppService _recipeAppService;

public CurdController(ITAppService recipeAppService)
{
_recipeAppService = recipeAppService;
}

[HttpPost]
[Route("Create")]

public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
return await _recipeAppService.CreateAsync(input);
}

[HttpDelete]
[Route("Delete")]

public virtual async Task DeleteAsync(TKey id)
{
await _recipeAppService.DeleteAsync(id);
}

[HttpGet]
[Route("GetAll")]

public virtual async Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
{
return await _recipeAppService.GetAllAsync(input);
}

[HttpGet]
[Route("Get")]

public virtual async Task<TGetOutputDto> GetAsync(TKey id)
{
return await _recipeAppService.GetAsync(id);
}

[HttpPut]
[Route("Update")]

public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
{
return await _recipeAppService.UpdateAsync(input);
}
}

[可选]替换RESTfulApi

为了兼容旧版Abp,需更改增删查改服务(CrudAppService)的方法签名,可参考[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序,此处不再赘述。

将UpdateAsync,GetListAsync方法封闭:

1
2
3
4
5
6
7
8
9
10
private new Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
return base.UpdateAsync(id, input);
}
private new Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
return base.GetListAsync(input);
}


封闭原有UpdateAsync, 新增的UpdateAsync方法更改了方法签名:

1
2
3
4
5
6
7
8
9
10
11

public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync((input as IEntityDto<TKey>).Id);
MapToEntity(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity);

}

Brief是一种简化的查询实体集合的方法,其返回的Dto不包含导航属性,以减少数据传输量。

新增GetAllAsync和GetAllBriefAsync方法:

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
29
30
31
32
33
34
public virtual Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
{
return this.GetListAsync(input);
}


public async Task<PagedResultDto<TGetListBriefOutputDto>> GetAllBriefAsync(TGetListBriefInput input)
{
await CheckGetListPolicyAsync();

var query = await CreateBriefFilteredQueryAsync(input);
var totalCount = await AsyncExecuter.CountAsync(query);

var entities = new List<TEntity>();
var entityDtos = new List<TGetListBriefOutputDto>();

if (totalCount > 0)
{
query = ApplySorting(query, input);
query = ApplyPaging(query, input);

entities = await AsyncExecuter.ToListAsync(query);

entityDtos = ObjectMapper.Map<List<TEntity>, List<TGetListBriefOutputDto>>(entities);

}

return new PagedResultDto<TGetListBriefOutputDto>(
totalCount,
entityDtos
);

}

扩展泛型参数

目前为止,我们的应用层基类继承于Abp.Application.Services.CrudAppService

为了更好的代码重用,我们对泛型参数进行扩展,使用CurdAppServiceBase的类可根据实际业务需求选择泛型参数

其中的泛型参数:

  • TEntity: CRUD操作对应的实体类
  • TEntityDto: GetAll方法返回的实体Dto
  • TKey: 实体的主键
  • TGetListBriefInput: GetAllBrief方法的输入参数
  • TGetListBriefOutputDto: GetAllBrief方法的输出参数

首先扩展ICurdAppService:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

public interface ICurdAppService<TEntityDto, in TKey>
: ICurdAppService<TEntityDto, TKey, PagedAndSortedResultRequestDto>
{

}

public interface ICurdAppService<TEntityDto, in TKey, in TGetListInput>
: ICurdAppService<TEntityDto, TKey, TGetListInput, TEntityDto>
{

}

public interface ICurdAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput>
: ICurdAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
{

}


public interface ICurdAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: ICurdAppService<TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
{

}


public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: ICurdAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput>
{

}



public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TGetListBriefInput, in TCreateInput, in TUpdateInput>
: ICurdAppService<TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
{

}



public interface ICurdAppService<TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, in TKey, in TGetListInput, in TGetListBriefInput, in TCreateInput, in TUpdateInput>

{
Task<TGetOutputDto> GetAsync(TKey id);

Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input);

Task<TGetOutputDto> CreateAsync(TCreateInput input);

Task<TGetOutputDto> UpdateAsync(TUpdateInput input);

Task DeleteAsync(TKey id);

Task<PagedResultDto<TGetListBriefOutputDto>> GetAllBriefAsync(TGetListInput input);


}




扩展CurdAppServiceBase:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey>
: CurdAppServiceBase<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}
}

public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput>
: CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}
}


public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
: CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}
}

public abstract class CurdAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdAppServiceBase<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}

protected override Task<TEntityDto> MapToGetListOutputDtoAsync(TEntity entity)
{
return MapToGetOutputDtoAsync(entity);
}

protected override TEntityDto MapToGetListOutputDto(TEntity entity)
{
return MapToGetOutputDto(entity);
}
}

public abstract class CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}
}


public abstract class CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected CurdAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{

}
}

扩展CurdController

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
public abstract class CurdController<ITAppService, TEntityDto, TKey>
: CurdController<ITAppService, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where ITAppService : ICurdAppService<TEntityDto, TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CurdController(ITAppService appService)
: base(appService)
{

}
}

public abstract class CurdController<ITAppService, TEntityDto, TKey, TGetListInput>
: CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TEntityDto>
where ITAppService : ICurdAppService<TEntityDto, TKey, TGetListInput>
where TEntityDto : IEntityDto<TKey>
{
protected CurdController(ITAppService appService)
: base(appService)
{

}
}


public abstract class CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput>
: CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where ITAppService : ICurdAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
where TEntityDto : IEntityDto<TKey>
{
protected CurdController(ITAppService appService)
: base(appService)
{

}
}



public abstract class CurdController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdController<ITAppService, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where ITAppService : ICurdAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntityDto : IEntityDto<TKey>
{
protected CurdController(ITAppService appService)
: base(appService)
{

}

}




public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListInput, TCreateInput, TUpdateInput>
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected CurdController(ITAppService appService)
: base(appService)
{

}

}



public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
: CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
where TGetListBriefInput : TGetListInput
{
protected CurdController(ITAppService appService)
: base(appService)
{

}

}


public abstract class CurdController<ITAppService, TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
: AbpControllerBase
where ITAppService : ICurdAppService<TGetOutputDto, TGetListOutputDto, TGetListBriefOutputDto, TKey, TGetListInput, TGetListBriefInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
where TGetListBriefInput : TGetListInput
{

private readonly ITAppService _recipeAppService;

public CurdController(ITAppService recipeAppService)
{
_recipeAppService = recipeAppService;
}

[HttpPost]
[Route("Create")]

public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
return await _recipeAppService.CreateAsync(input);
}

[HttpDelete]
[Route("Delete")]

public virtual async Task DeleteAsync(TKey id)
{
await _recipeAppService.DeleteAsync(id);
}

[HttpGet]
[Route("GetAll")]

public virtual async Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
{
return await _recipeAppService.GetAllAsync(input);
}

[HttpGet]
[Route("Get")]

public virtual async Task<TGetOutputDto> GetAsync(TKey id)
{
return await _recipeAppService.GetAsync(id);
}

[HttpPut]
[Route("Update")]

public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
{
return await _recipeAppService.UpdateAsync(input);
}

[HttpGet]
[Route("GetAllBrief")]

public virtual async Task<PagedResultDto<TGetListBriefOutputDto>> GetAllBriefAsync(TGetListBriefInput input)
{
return await _recipeAppService.GetAllBriefAsync(input);
}
}


服务的“渐进式”

在开发业务模块时,我们可以先使用简单的方式提供Curd服务,随着UI复杂度增加,逐步的使用更加复杂的Curd服务。某种程度上来说,即所谓“渐进式”的开发方式。

  • BaseCurd: 基础型,仅包含Create、Update、Delete、Get
  • SimpleCurd: 简单型,包含BaseCurd的所有功能,同时包含GetAll
  • Curd:完整型,包含SimpleCurd的所有功能,同时包含GetAllBrief,GetAllBrief是一种简化的查询实体集合的方法,其返回的Dto不包含导航属性,以减少数据传输量。是最常用的服务类型。
  • ExtendedCurd:扩展型,包含Curd的所有功能,同时包含GetAllBriefWithoutPage,GetAllBriefWithoutPage 适合一些非分页场景,如日历视图,Echarts图表控件等。使用此接口需要注意:由于没有分页限制,需要其他的查询约束条件(比如日期范围),否则会返回大量的数据,影响性能。

我们扩展应用层基类,控制器及其接口

在这里插入图片描述

在这里插入图片描述

使用

以Alarm为例。来实现扩展型Curd服务(ExtendedCurd)。

假设你已完成创建实体、Dto以及配置完成AutoMapper映射

  1. 在Health模块的抽象层中,创建接口IAlarmAppService,继承自IExtendedCurdAppService和IApplicationService。

IApplicationService是ABP框架的接口,所有的应用服务都需要继承自此接口。

1
2
3
public interface IAlarmAppService : IExtendedCurdAppService<AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput,  CreateAlarmInput, UpdateAlarmInput>, IApplicationService
{
}
  1. 在Health模块的应用层中,创建AlarmAppService
1
2
3
4
5
6
7
public class AlarmAppService : ExtendedCurdAppServiceBase<CAH.Health.Alarm.Alarm, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput>, IAlarmAppService
{
public AlarmAppService(IRepository<CAH.Health.Alarm.Alarm, long> basicInventoryRepository) : base(basicInventoryRepository)
{
}
}

  1. 在Health模块的HttpApi中,创建AlarmController,并实现IAlarmAppService接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Area(HealthRemoteServiceConsts.ModuleName)]
[RemoteService(Name = HealthRemoteServiceConsts.RemoteServiceName)]
[Route("api/Health/alarm")]
public class AlarmController : ExtendedCurdController<IAlarmAppService, AlarmDto, AlarmDto, AlarmBriefDto, long, GetAllAlarmInput, GetAllAlarmInput, CreateAlarmInput, UpdateAlarmInput>, IAlarmAppService
{
private readonly IAlarmAppService _alarmAppService;

public AlarmController(IAlarmAppService alarmAppService) : base(alarmAppService)
{
_alarmAppService = alarmAppService;
}


}

运行程序

在这里插入图片描述

可以看到,我们的接口已经包含所有扩展型Curd方法。

下一章我们将实现通用查询接口的按组织架构查询。

怎样优雅地增删查改(四):创建通用查询基类

https://blog.matoapp.net/posts/3fbe6877/

作者

林晓lx

发布于

2023-07-13

更新于

2024-09-11

许可协议

评论