From a07484570f806a5d891d13ea33d616c804a72184 Mon Sep 17 00:00:00 2001 From: Mohamed Nazem <109486038+nazem0@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:07:18 +0300 Subject: [PATCH] feat: add IQueryable DTO projection support to read-only application services --- .../Services/AbstractKeyReadOnlyAppService.cs | 50 +++++++++++++++++-- .../Services/ReadOnlyAppService.cs | 11 +++- .../ObjectMapping/IQueryProjectionMapper.cs | 8 +++ .../ObjectMapping/QueryProjectionMapper.cs | 7 +++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjectionMapper.cs create mode 100644 framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/QueryProjectionMapper.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs index 3791c4202f..0c53ac2705 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs @@ -44,6 +44,18 @@ public abstract class AbstractKeyReadOnlyAppService? ObjectProjectionMapper => + LazyServiceProvider.LazyGetService>(); + + protected virtual IQueryProjectionMapper? ListProjectionMapper => + LazyServiceProvider.LazyGetService>(); + + protected virtual bool UseObjectProjectionMapper => + ObjectProjectionMapper != null; + + protected virtual bool UseListProjectionMapper => + ListProjectionMapper != null; + protected AbstractKeyReadOnlyAppService(IReadOnlyRepository repository) { ReadOnlyRepository = repository; @@ -53,6 +65,19 @@ public abstract class AbstractKeyReadOnlyAppService(); var entityDtos = new List(); if (totalCount > 0) @@ -73,8 +97,19 @@ public abstract class AbstractKeyReadOnlyAppService( @@ -85,6 +120,13 @@ public abstract class AbstractKeyReadOnlyAppService GetEntityByIdAsync(TKey id); + protected virtual Task> GetEntityByIdQueryAsync(TKey id) + { + throw new NotImplementedException( + "Override this method to create the query used for getting an entity by id." + ); + } + protected virtual async Task CheckGetPolicyAsync() { await CheckPolicyAsync(GetPolicyName); @@ -227,4 +269,4 @@ public abstract class AbstractKeyReadOnlyAppService(entity); } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs index 867ca35ad4..0cfbdee673 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs @@ -37,7 +37,7 @@ public abstract class ReadOnlyAppService Repository { get; } protected ReadOnlyAppService(IReadOnlyRepository repository) - : base(repository) + : base(repository) { Repository = repository; } @@ -47,6 +47,13 @@ public abstract class ReadOnlyAppService> GetEntityByIdQueryAsync(TKey id) + { + var query = await Repository.GetQueryableAsync(); + + return query.Where(e => e.Id.Equals(id)); + } + protected override IQueryable ApplyDefaultSorting(IQueryable query) { if (typeof(TEntity).IsAssignableTo()) @@ -58,4 +65,4 @@ public abstract class ReadOnlyAppService e.Id); } } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjectionMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjectionMapper.cs new file mode 100644 index 0000000000..b40ea1c198 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjectionMapper.cs @@ -0,0 +1,8 @@ +using System.Linq; +using Volo.Abp.DependencyInjection; +namespace Volo.Abp.ObjectMapping; + +public interface IQueryProjectionMapper : ITransientDependency +{ + IQueryable ProjectTo(IQueryable source); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/QueryProjectionMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/QueryProjectionMapper.cs new file mode 100644 index 0000000000..96e9c98c3a --- /dev/null +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/QueryProjectionMapper.cs @@ -0,0 +1,7 @@ +using System.Linq; +namespace Volo.Abp.ObjectMapping; + +public abstract class QueryProjectionMapper : IQueryProjectionMapper +{ + public abstract IQueryable ProjectTo(IQueryable source); +} \ No newline at end of file