In the ABP development framework, some common standard processing interfaces such as GetAll, Get, Create, Update and Delete will be provided in the ApplicationService class of the application service layer. When the ApplicationService class is defined, several different types will be passed in as generic parameters to realize strong type type processing, This essay gives a detailed introduction to the implementation and processing of paging query sorting, including the definition of paging query conditions, subclass application service layer condition query logic rewriting, sorting logic rewriting and other rules.
1. Generic definition of ApplicationService class
For example, when we define the UserApplicationService of the User application service layer, we pass in several different types of parameters as the generic constraint type of the base class, as shown below.
[AbpAuthorize] public class UserAppService : MyAsyncServiceBase<User, UserDto, long, UserPagedDto, CreateUserDto, UserDto>, IUserAppService
The definition of the dictionary data application service layer of the same type is as follows, which is similar to UserAppService.
MyAsyncServiceBase is a user-defined base class object, which is mainly used to construct different strongly typed objects according to different parameters.
public abstract class MyAsyncServiceBase<TEntity, TEntityDto, TPrimaryKey, TGetAllInput, TCreateInput, TUpdateInput, TGetInput, TDeleteInput> : AsyncCrudAppService<TEntity, TEntityDto, TPrimaryKey, TGetAllInput, TCreateInput, TUpdateInput, TGetInput, TDeleteInput> where TEntity : class, IEntity<TPrimaryKey> where TEntityDto : IEntityDto<TPrimaryKey> where TUpdateInput : IEntityDto<TPrimaryKey> where TGetInput : IEntityDto<TPrimaryKey> where TDeleteInput : IEntityDto<TPrimaryKey>
Here, the User class of the parameter in the service layer of UserApplicationService corresponds to the domain object of EFCore. Its definition is as follows
public class User : AbpUser<User>
Because User needs to integrate some features of AbpUser base class, it has inheritance relationship. It is mainly the object responsible for dealing with the database model.
If it is not a base class object used by a system like User, we need to define it as follows, specify the name of the form and the constraints of the object. The domain object of the following dictionary is defined as follows.
and The second parameter of MyAsyncServiceBase is the DTO object used for transmission. It can be considered that it has no direct relationship with the database. However, due to the introduction of AutoMapper, we generally see that their properties still have many similarities, but DTO is more oriented to the business interface rather than storage processing.
If some special interface data information needs to be converted into domain object attributes, special user-defined mapping processing is required.
The DTO object definition of User is as follows.
If our DTO object does not need ABP to constrain the parameter content, some conditions can be more simplified, as shown in the dictionary DTO object below.
For the application service layer definition similar to the following dictionary module
The third parameter is the type of primary key ID. if it is Int, it is an integer. Here it is a string type, so string is used.
The fourth parameter dictdatapageddo is the condition of paging query , This DTO object is mainly used to obtain the query processing conditions of the client, so it can be tailored according to the query conditions. By default, the attributes generated by the code generation tool Database2sharp basically include all database table attribute names. For example, the query criteria of dictionary data are relatively simple. As shown below, in addition to some paging condition information, it contains the required query condition attributes.
/// <summary> /// Used for paging queries based on criteria /// </summary> public class DictDataPagedDto : PagedAndSortedInputDto { public DictDataPagedDto() : base() { } /// <summary> /// Parameterized constructor /// </summary> /// <param name="skipCount">Number of skipped</param> /// <param name="resultCount">Maximum number of result sets</param> public DictDataPagedDto(int skipCount, int resultCount) : base(skipCount, resultCount) { } /// <summary> /// Initialization using paging information SkipCount and MaxResultCount /// </summary> /// <param name="pagerInfo">Paging information</param> public DictDataPagedDto(PagerInfo pagerInfo) : base(pagerInfo) { } /// <summary> /// Dictionary type ID /// </summary> public virtual string DictType_ID { get; set; } /// <summary> /// Type name /// </summary> public virtual string Name { get; set; } /// <summary> /// Specified value /// </summary> public virtual string Value { get; set; } /// <summary> /// remarks /// </summary> public virtual string Remark { get; set; } }
2. Implementation and processing of paging query sorting
Earlier, we introduced the parameter definition of generic base classes in the application service layer, which can strongly return various data interfaces. This is a very flexible design pattern.
ABP+Swagger is responsible for the development and publication of API interfaces. The following is the management interface of API interfaces.
Further check the API interface description of GetAll. We can see the corresponding condition parameters, as shown below.
These are processed as query conditions to obtain the corresponding condition information for the back end, so as to filter the returned data records.
Then our front-end interface also needs to construct the query interface according to these parameters. We can process it through some conditions, where MaxResultCount and SkipCount are parameters for paging positioning.
Let's take a look at the processing function of the base class for query paging sorting, so as to understand its processing rules.
public virtual async Task<PagedResultDto<TEntityDto>> GetAllAsync(TGetAllInput input) { //Judgment authority CheckGetAllPermission(); //Get the condition of paging query var query = CreateFilteredQuery(input); //Get the number of all records according to the condition var totalCount = await AsyncQueryableExecuter.CountAsync(query); //Sorting and pagination of query contents query = ApplySorting(query, input); query = ApplyPaging(query, input); //Returns the domain entity object var entities = await AsyncQueryableExecuter.ToListAsync(query); //Construct the returned result set and convert the entity class to DTO corresponding return new PagedResultDto<TEntityDto>( totalCount, entities.Select(MapToEntityDto).ToList() ); }
among CreateFilteredQuery, ApplySorting, and ApplyPaging uses functions that can be overridden by subclasses to realize flexible logic adjustment processing.
In the base class, the default is CreateFilteredQuery It provides a simple process of returning all lists without processing query conditions. This specific condition filtering is logically implemented by subclasses.
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetAllInput input) { return Repository.GetAll(); }
The list Sorting process is the base class function of ApplySorting. The base class provides standard conditional Sorting for Sorting attributes. Otherwise, it will be sorted in reverse order according to the primary key ID, as shown in the following code.
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetAllInput input) { //Try to sort query if available var sortInput = input as ISortedResultRequest; if (sortInput != null) { if (!sortInput.Sorting.IsNullOrWhiteSpace()) { return query.OrderBy(sortInput.Sorting); } } //IQueryable.Task requires sorting, so we should sort if Take will be used. if (input is ILimitedResultRequest) { return query.OrderByDescending(e => e.Id); } //No sorting return query; }
The basic class's paging processing ApplyPaging logic is mainly converted to a standard interface for processing, as shown in the following code.
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetAllInput input) { //Try to use paging if available var pagedInput = input as IPagedResultRequest; if (pagedInput != null) { return query.PageBy(pagedInput); } //Try to limit query result if available var limitedInput = input as ILimitedResultRequest; if (limitedInput != null) { return query.Take(limitedInput.MaxResultCount); } //No paging return query; }
The above are several default implementations that can be overridden provided by the standard base class. Generally speaking, we will override the logic implementation by subclassing.
For example, the condition information of the dictionary module can be rewritten to realize custom condition query processing, as shown in the rewriting of DictDataAppService application service layer.
/// <summary> /// Custom condition processing /// </summary> /// <param name="input"></param> /// <returns></returns> protected override IQueryable<DictData> CreateFilteredQuery(DictDataPagedDto input) { return base.CreateFilteredQuery(input) .WhereIf(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) .WhereIf(!string.IsNullOrEmpty(input.Remark), t => t.Remark.Contains(input.Remark)) .WhereIf(!string.IsNullOrEmpty(input.Value), t => t.Value == input.Value) .WhereIf(!string.IsNullOrEmpty(input.DictType_ID), t => t.DictType_ID == input.DictType_ID); }
For queries with complex attributes, we can appropriately adjust the processing base class of this function, which can generally be generated according to the code generation tool. We can fine tune the special conditions ourselves.
For example, the user application service layer class UserAppService rewrites the function of custom conditions. The code is as follows.
/// <summary> /// Custom condition processing /// </summary> /// <param name="input">query criteria Dto</param> /// <returns></returns> protected override IQueryable<User> CreateFilteredQuery(UserPagedDto input) { return Repository.GetAllIncluding(x => x.Roles) //base.CreateFilteredQuery(input) .WhereIf(input.ExcludeId.HasValue, t => t.Id != input.ExcludeId) //Exclude ID .WhereIf(!input.EmailAddress.IsNullOrWhiteSpace(), t => t.EmailAddress.Contains(input.EmailAddress)) //If exact matching is required, use Equals .WhereIf(input.IsActive.HasValue, t => t.IsActive == input.IsActive) //If exact matching is required, use Equals .WhereIf(input.IsEmailConfirmed.HasValue, t => t.IsEmailConfirmed == input.IsEmailConfirmed) //If exact matching is required, use Equals .WhereIf(input.IsPhoneNumberConfirmed.HasValue, t => t.IsPhoneNumberConfirmed == input.IsPhoneNumberConfirmed) //If exact matching is required, use Equals .WhereIf(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //If exact matching is required, use Equals .WhereIf(!input.PhoneNumber.IsNullOrWhiteSpace(), t => t.PhoneNumber.Contains(input.PhoneNumber)) //If exact matching is required, use Equals .WhereIf(!input.Surname.IsNullOrWhiteSpace(), t => t.Surname.Contains(input.Surname)) //If exact matching is required, use Equals .WhereIf(!input.UserName.IsNullOrWhiteSpace(), t => t.UserName.Contains(input.UserName)) //If exact matching is required, use Equals .WhereIf(!input.UserNameOrEmailAddress.IsNullOrWhiteSpace(), t => t.UserName.Contains(input.UserNameOrEmailAddress) || t.EmailAddress.Contains(input.UserNameOrEmailAddress) || t.FullName.Contains(input.UserNameOrEmailAddress)) //Create date range query .WhereIf(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value) .WhereIf(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value); }
It can be seen that more processing conditions will be added according to the properties of UserPageDto. Some are exact matching, some are fuzzy matching, and some, such as date, are range matching.
For attributes with interval ranges such as values and dates, we often have a Start and End Start value parameter in the conditional DTO object.
In this way, when using the front end of Vue & element to query, we can construct the corresponding interval parameters, as shown in the front end code below.
Sometimes, in order to simplify the date range code at the front end, we can simplify the processing through auxiliary classes.
For custom sorting, it can be sorted according to actual needs. For self growing ID types, it is not a problem to use ID reverse order display. If it is a string type and itself is a GUID type, it is meaningless to use ID class sorting. Therefore, logical rewriting must be realized by rewriting the base class function.
/// <summary> /// Custom sort processing /// </summary> /// <param name="query"></param> /// <param name="input"></param> /// <returns></returns> protected override IQueryable<DictData> ApplySorting(IQueryable<DictData> query, DictDataPagedDto input) { //Sort by dictionary type first, and then sort by under the same dictionary type Seq sort return base.ApplySorting(query, input).OrderBy(s=>s.DictType_ID).ThenBy(s => s.Seq); }
The specific situation can be sorted according to the characteristics of ID or the specific situation of sorting.
The last item is paging, which can be handled in a standard way. It can not be rewritten by default.
In this way, we can rewrite part of the logic of several functions mentioned earlier according to the actual situation, so as to realize the rules of condition processing, sorting processing, paging processing and so on.
public virtual async Task<PagedResultDto<TEntityDto>> GetAllAsync(TGetAllInput input) { //Judgment authority CheckGetAllPermission(); //Get the condition of paging query var query = CreateFilteredQuery(input); //Get the number of all records according to the condition var totalCount = await AsyncQueryableExecuter.CountAsync(query); //Sorting and pagination of query contents query = ApplySorting(query, input); query = ApplyPaging(query, input); //Returns the domain entity object var entities = await AsyncQueryableExecuter.ToListAsync(query); //Construct the returned result set and convert the entity class to DTO corresponding return new PagedResultDto<TEntityDto>( totalCount, entities.Select(MapToEntityDto).ToList() ); }
Therefore, the Winform end or the BS front end of Vue & element can quickly query and sort through different condition information.
The list interface of menu resource management is as follows:
The user list includes pagination query and list display, and you can use buttons to add, edit, view user records, or reset passwords for specified users.