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
+98Lines changed: 98 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -444,6 +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
+
*`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.
447
448
*`DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default.
448
449
449
450
#### Object to Object Mapping
@@ -456,6 +457,103 @@ These methods are used to convert Entities to DTOs and vice verse. They use the
456
457
*`MapToEntityAsync(TCreateInput)` is used to create an entity from `TCreateInput`.
457
458
*`MapToEntityAsync(TUpdateInput, TEntity)` is used to update an existing entity from `TUpdateInput`.
458
459
460
+
#### Query Projection
461
+
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
+
464
+
Implement the `IQueryProjector<TEntity, TDto>` interface to define a projection:
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. Your existing maps are not used for the projection, a projector is always a class implementing `IQueryProjector<TSource, TDestination>`.
486
+
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, and use the `ReplaceServices` option of the `DependencyAttribute` to replace an existing one. Filters (like soft delete and multi-tenancy), sorting and paging are still applied to the query before the projection.
488
+
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,
533
+
and the provider has to be able to translate the join. The one row per entity rule above applies here too,
534
+
that's why the example uses a left join. A joined column can not be used for the sorting, and the paging is
535
+
based on the entity query, since both are applied before this method is called.
536
+
537
+
A projector is resolved by the `(entity, DTO)` type pair, just like an `IObjectMapper<TSource, TDestination>`, so registering one enables the projection for every application service using that pair. It replaces the way the DTOs are read:
538
+
539
+
*`GetListAsync` doesn't use `MapToGetListOutputDtosAsync` anymore.
540
+
*`GetAsync` doesn't use `GetEntityByIdAsync` and `MapToGetOutputDtoAsync` anymore, as long as the application service can create a query for a single entity. `ReadOnlyAppService` and `CrudAppService` already do that. A class deriving from `AbstractKeyReadOnlyAppService` has to override `CreateEntityQueryOrNullAsync`, otherwise `GetAsync` keeps loading the entity and mapping it.
541
+
542
+
The rest of the pipeline is untouched. The authorization policies are still checked, `CreateFilteredQueryAsync`, `ApplySorting` and `ApplyPaging` are still used, the data filters (like soft delete and multi-tenancy) are still applied, and the create, update and delete methods still use the [IObjectMapper](../../infrastructure/object-to-object-mapping.md).
543
+
544
+
> If an application service needs to keep using the entity based extension points, override the `GetOutputDtoQueryProjector` or `GetListOutputDtoQueryProjector` property and return `null`:
Copy file name to clipboardExpand all lines: framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginScriptBundleContributor.cs
Copy file name to clipboardExpand all lines: framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginStyleBundleContributor.cs
0 commit comments