You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/en/framework/architecture/domain-driven-design/application-services.md
+44-7Lines changed: 44 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,4 +1,4 @@
1
-
```json
1
+
```json
2
2
//[doc-seo]
3
3
{
4
4
"Description": "Learn how to implement application services in the ABP Framework to expose domain logic and streamline presentation layer interactions."
@@ -444,7 +444,7 @@ These methods are low level methods that can control how to query entities from
444
444
*`ApplyPaging` is used to make paging on the query. If your `TGetListInput` already implements `IPagedResultRequest`, you don't need to override this since the ABP automatically understands it and performs the paging.
445
445
*`ApplySorting` is used to sort (order by...) the query. If your `TGetListInput` already implements the `ISortedResultRequest`, ABP automatically sorts the query. If not, it fallbacks to the `ApplyDefaultSorting` which tries to sort by creation time, if your entity implements the standard `IHasCreationTime` interface.
446
446
*`GetEntityByIdAsync` is used to get an entity by id, which calls `Repository.GetAsync(id)` by default.
447
-
*`CreateEntityQueryAsync` is used to create a query for a single entity by id, which is only needed for the *Query Projection* explained below. It returns `null` if the application service can not create such a query, then `GetEntityByIdAsync` is used.
447
+
*`CreateEntityQueryOrNullAsync` is used to create a query for a single entity by id, which is only needed for the *Query Projection* explained below. It returns `null` if the application service can not create such a query, then `GetEntityByIdAsync` is used.
448
448
*`DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default.
449
449
450
450
#### Object to Object Mapping
@@ -461,15 +461,15 @@ These methods are used to convert Entities to DTOs and vice verse. They use the
461
461
462
462
`GetAsync` and `GetListAsync` get the entities from the database, then map them to DTOs in the memory. If your DTO uses only a few properties of a large entity, you can project the query to the DTO instead, so the database returns only the columns you need.
463
463
464
-
Implement the `IQueryableMapper<TEntity, TDto>` interface to define a projection:
464
+
Implement the `IQueryProjector<TEntity, TDto>` interface to define a projection:
@@ -484,14 +484,51 @@ public class BookProjector : IQueryableMapper<Book, BookDto>
484
484
485
485
You don't have to write the `Select` by hand. Both [Mapperly](https://mapperly.riok.app/) and [AutoMapper](https://docs.automapper.org) can project an `IQueryable`, refer to their own documentation for it and to the [object to object mapping document](../../infrastructure/object-to-object-mapping.md) for their ABP integrations.
486
486
487
-
ABP registers the projection mappers by convention, you don't need to configure anything else. Filters (like soft delete and multi-tenancy), sorting and paging are still applied to the query before the projection.
487
+
ABP registers the projectors by convention, you don't need to configure anything else. Implement a projector once for an entity and DTO pair, the last registered one is used otherwise. Filters (like soft delete and multi-tenancy), sorting and paging are still applied to the query before the projection.
488
488
489
-
> The projection replaces the entity based extension points. `GetAsync` doesn't use `GetEntityByIdAsync` and `MapToGetOutputDtoAsync`, `GetListAsync` doesn't use `MapToGetListOutputDtosAsync` anymore. If an application service needs to keep using them, override the `GetQueryableMapper` or `GetListQueryableMapper` property and return `null`:
489
+
> A projection must return one row per entity. The total count and the paging are calculated on the entity query before the projection runs, so a projection that filters out rows (an inner join to an optional relation) or multiplies them (a join to a collection) returns a page that doesn't match the reported total count. Use a left join for optional relations.
490
+
491
+
The projector is synchronous, so it can not obtain the query of another aggregate root, which is only
492
+
available through the asynchronous `GetQueryableAsync`. Override `CreateGetOutputDtoQueryOrNullAsync` or
493
+
`CreateGetListOutputDtoQueryOrNullAsync` for that. They replace the projector for that application service:
Both queries must come from the same database context, otherwise they can not be executed as a single query,
520
+
and the provider has to be able to translate the join. The one row per entity rule above applies here too,
521
+
that's why the example uses a left join. A joined column can not be used for the sorting and the paging,
522
+
since they are already applied to the entity query before this method is called.
523
+
524
+
> The projection replaces the entity based extension points. `GetAsync` doesn't use `GetEntityByIdAsync` and `MapToGetOutputDtoAsync`, `GetListAsync` doesn't use `MapToGetListOutputDtosAsync` anymore. Projectors are resolved by the `(entity, DTO)` type pair, so registering one enables the projection for every application service using that pair. If an application service needs to keep using the entity based extension points, override the `GetOutputDtoQueryProjector` or `GetListOutputDtoQueryProjector` property and return `null`:
0 commit comments