Skip to content

Commit a074845

Browse files
authored
feat: add IQueryable DTO projection support to read-only application services
1 parent 516e308 commit a074845

4 files changed

Lines changed: 70 additions & 6 deletions

File tree

framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
4444

4545
protected virtual string? GetListPolicyName { get; set; }
4646

47+
protected virtual IQueryProjectionMapper<TEntity, TGetOutputDto>? ObjectProjectionMapper =>
48+
LazyServiceProvider.LazyGetService<IQueryProjectionMapper<TEntity, TGetOutputDto>>();
49+
50+
protected virtual IQueryProjectionMapper<TEntity, TGetListOutputDto>? ListProjectionMapper =>
51+
LazyServiceProvider.LazyGetService<IQueryProjectionMapper<TEntity, TGetListOutputDto>>();
52+
53+
protected virtual bool UseObjectProjectionMapper =>
54+
ObjectProjectionMapper != null;
55+
56+
protected virtual bool UseListProjectionMapper =>
57+
ListProjectionMapper != null;
58+
4759
protected AbstractKeyReadOnlyAppService(IReadOnlyRepository<TEntity> repository)
4860
{
4961
ReadOnlyRepository = repository;
@@ -53,6 +65,19 @@ public virtual async Task<TGetOutputDto> GetAsync(TKey id)
5365
{
5466
await CheckGetPolicyAsync();
5567

68+
var projectionMapper = ObjectProjectionMapper;
69+
70+
if (UseObjectProjectionMapper && projectionMapper != null)
71+
{
72+
var query = await GetEntityByIdQueryAsync(id);
73+
74+
var dto =
75+
await AsyncExecuter.FirstOrDefaultAsync(projectionMapper.ProjectTo(query))
76+
?? throw new EntityNotFoundException(typeof(TEntity), id);
77+
78+
return dto;
79+
}
80+
5681
var entity = await GetEntityByIdAsync(id);
5782

5883
return await MapToGetOutputDtoAsync(entity);
@@ -65,16 +90,26 @@ public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetLi
6590
var query = await CreateFilteredQueryAsync(input);
6691
var totalCount = await AsyncExecuter.CountAsync(query);
6792

68-
var entities = new List<TEntity>();
6993
var entityDtos = new List<TGetListOutputDto>();
7094

7195
if (totalCount > 0)
7296
{
7397
query = ApplySorting(query, input);
7498
query = ApplyPaging(query, input);
7599

76-
entities = await AsyncExecuter.ToListAsync(query);
77-
entityDtos = await MapToGetListOutputDtosAsync(entities);
100+
var projectionMapper = ListProjectionMapper;
101+
102+
if (UseListProjectionMapper && projectionMapper != null)
103+
{
104+
entityDtos = await AsyncExecuter.ToListAsync(
105+
projectionMapper.ProjectTo(query)
106+
);
107+
}
108+
else
109+
{
110+
var entities = await AsyncExecuter.ToListAsync(query);
111+
entityDtos = await MapToGetListOutputDtosAsync(entities);
112+
}
78113
}
79114

80115
return new PagedResultDto<TGetListOutputDto>(
@@ -85,6 +120,13 @@ public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetLi
85120

86121
protected abstract Task<TEntity> GetEntityByIdAsync(TKey id);
87122

123+
protected virtual Task<IQueryable<TEntity>> GetEntityByIdQueryAsync(TKey id)
124+
{
125+
throw new NotImplementedException(
126+
"Override this method to create the query used for getting an entity by id."
127+
);
128+
}
129+
88130
protected virtual async Task CheckGetPolicyAsync()
89131
{
90132
await CheckPolicyAsync(GetPolicyName);
@@ -227,4 +269,4 @@ protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity)
227269
{
228270
return ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);
229271
}
230-
}
272+
}

framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public abstract class ReadOnlyAppService<TEntity, TGetOutputDto, TGetListOutputD
3737
protected IReadOnlyRepository<TEntity, TKey> Repository { get; }
3838

3939
protected ReadOnlyAppService(IReadOnlyRepository<TEntity, TKey> repository)
40-
: base(repository)
40+
: base(repository)
4141
{
4242
Repository = repository;
4343
}
@@ -47,6 +47,13 @@ protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
4747
return await Repository.GetAsync(id);
4848
}
4949

50+
protected override async Task<IQueryable<TEntity>> GetEntityByIdQueryAsync(TKey id)
51+
{
52+
var query = await Repository.GetQueryableAsync();
53+
54+
return query.Where(e => e.Id.Equals(id));
55+
}
56+
5057
protected override IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
5158
{
5259
if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>())
@@ -58,4 +65,4 @@ protected override IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> q
5865
return query.OrderByDescending(e => e.Id);
5966
}
6067
}
61-
}
68+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Linq;
2+
using Volo.Abp.DependencyInjection;
3+
namespace Volo.Abp.ObjectMapping;
4+
5+
public interface IQueryProjectionMapper<TSource, TDestination> : ITransientDependency
6+
{
7+
IQueryable<TDestination> ProjectTo(IQueryable<TSource> source);
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.Linq;
2+
namespace Volo.Abp.ObjectMapping;
3+
4+
public abstract class QueryProjectionMapper<TSource, TDestination> : IQueryProjectionMapper<TSource, TDestination>
5+
{
6+
public abstract IQueryable<TDestination> ProjectTo(IQueryable<TSource> source);
7+
}

0 commit comments

Comments
 (0)