在ABP開發框架中應用服務層ApplicationService類中,都會提供常見的一些如GetAll、Get、Create、Update、Delete等的標准處理接口,而由於在ApplicationService類定義的時候,都會傳入幾個不同的類型作為泛型的參數,實現強類型的類型處理,本篇隨筆對於分頁查詢排序的實現處理做一個詳細的介紹,介紹其中對分頁查詢條件的定義,子類應用服務層的條件查詢邏輯重寫、排序邏輯重寫等規則的處理。
1、ApplicationService類的泛型定義
例如我們定義User應用服務層的UserApplicationService的時候,傳入了幾個不同類型的參數作為基類的泛型約束類型,如下所示。
[AbpAuthorize] public class UserAppService : MyAsyncServiceBase<User, UserDto, long, UserPagedDto, CreateUserDto, UserDto>, IUserAppService
同類型的字典數據應用服務層的定義如下所示,可以看到和UserAppService類似的。
其中MyAsyncServiceBase則是我們自定義的一個基類對象,主要是根據傳入不同的參數構造不同的強類型對象返回。
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>
這里UserApplicationService的服務層中參數的User類,對應是EFCore的領域對象,它的定義如下所示
public class User : AbpUser<User>
由於User需要集成AbpUser基類的一些特性,因此有繼承關系,它主要就是負責和數據庫模型打交道的對象。
而如果不是類似User這樣系統用到的基類對象,那么我們就需要如下定義,指定表單的名稱,以及對象的約束條件了,如下字典的領域對象如下定義所示。
而 MyAsyncServiceBase 的第二個參數則是用於傳遞的DTO對象,可以認為它和數據庫沒有直接的關系,不過由於引入了AutoMapper,我們一般看它們的屬性還是有很多相同的地方,不過DTO更加面向的是業務界面,而非存儲處理。
如果對於一些界面特殊的數據信息,需要轉換為領域對象的屬性,則需要進行特別的自定義映射處理了。
如User的DTO對象定義如下所示。
而如果我們的DTO對象,不需要利用ABP進行參數內容的約束,那么可以更加簡化一些條件,如下字典DTO對象所示。
對於類似下面的字典模塊的應用服務層定義
其中第三個參數是主鍵ID的類型,如果為Int這是整形,這里是字符串類型,因此使用string。
第四個參數DictDataPagedDto就是分頁查詢的條件 ,這個DTO對象,主要就是獲取客戶端查詢處理的條件的,因此可以根據需要查詢的條件進行裁剪,默認利用代碼生成工具Database2sharp生成的屬性基本上包括了所有的數據庫表屬性名稱了。如字典數據的查詢條件比較簡單,如下所示,除了包含一些分頁條件信息外,就是包含所需要的查詢條件屬性了。
/// <summary> /// 用於根據條件分頁查詢 /// </summary> public class DictDataPagedDto : PagedAndSortedInputDto { public DictDataPagedDto() : base() { } /// <summary> /// 參數化構造函數 /// </summary> /// <param name="skipCount">跳過的數量</param> /// <param name="resultCount">最大結果集數量</param> public DictDataPagedDto(int skipCount, int resultCount) : base(skipCount, resultCount) { } /// <summary> /// 使用分頁信息進行初始化SkipCount 和 MaxResultCount /// </summary> /// <param name="pagerInfo">分頁信息</param> public DictDataPagedDto(PagerInfo pagerInfo) : base(pagerInfo) { } /// <summary> /// 字典類型ID /// </summary> public virtual string DictType_ID { get; set; } /// <summary> /// 類型名稱 /// </summary> public virtual string Name { get; set; } /// <summary> /// 指定值 /// </summary> public virtual string Value { get; set; } /// <summary> /// 備注 /// </summary> public virtual string Remark { get; set; } }
2、分頁查詢排序的實現處理
前面我們介紹了應用服務層中利用泛型基類的參數定義,可以強類型返回各項不同數據接口,這種就是非常彈性化的設計模式了。
ABP+Swagger負責API接口的開發和公布,如下是API接口的管理界面。
進一步查看GetAll的API接口說明,我們可以看到對應的條件參數,如下所示。
這些是作為查詢條件的處理,用來給后端獲取對應的條件信息,從而過濾返回的數據記錄的。
那么我們前端界面也需要根據這些參數來構造查詢界面,我們可以通過部分條件進行處理即可,其中MaxResultCount和SkipCount是用於分頁定位的參數。
我們來看看基類對於查詢分頁排序的處理函數,從而了解它的處理規則。
public virtual async Task<PagedResultDto<TEntityDto>> GetAllAsync(TGetAllInput input) { //判斷權限 CheckGetAllPermission(); //獲取分頁查詢的條件 var query = CreateFilteredQuery(input); //根據條件獲取所有記錄數 var totalCount = await AsyncQueryableExecuter.CountAsync(query); //對查詢內容排序和分頁 query = ApplySorting(query, input); query = ApplyPaging(query, input); //返回領域實體對象 var entities = await AsyncQueryableExecuter.ToListAsync(query); //構造返回結果集,並轉換實體類為DTO對應 return new PagedResultDto<TEntityDto>( totalCount, entities.Select(MapToEntityDto).ToList() ); }
其中 CreateFilteredQuery 、ApplySorting和 ApplyPaging 都是利用可以子類重寫的函數實現彈性化的邏輯調整處理。
在基類中,默認的CreateFilteredQuery 提供了簡單的返回所有列表的處理,並不處理查詢條件,這個具體的條件過濾由子類實現邏輯的。
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetAllInput input) { return Repository.GetAll(); }
而列表排序處理ApplySorting的基類函數,基類提供了標准的對Sorting 屬性進行條件排序,否則就根據主鍵ID進行倒序排序處理,如下代碼所示。
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; }
而基類的分頁的處理ApplyPaging邏輯,主要就是轉換為標准的接口進行處理,如下代碼所示。
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; }
以上是標准基類提供的幾個可以重寫的默認實現,一般來說,我們會通過子類重寫邏輯實現的方式進行邏輯重寫的。
如對於字典模塊的條件信息,我們可以進行重寫,以便實現自定義的條件查詢處理,如下DictDataAppService應用服務層的重寫處理。
/// <summary> /// 自定義條件處理 /// </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); }
而對於屬性比較復雜的查詢,我們適當調整這個函數的處理基類,一般都可以根據代碼生成工具進行生成的,特殊條件自己微調一下就沒問題了。
如用戶應用服務層類UserAppService的重寫自定義條件的函數,代碼如下所示。
/// <summary> /// 自定義條件處理 /// </summary> /// <param name="input">查詢條件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) //不包含排除ID .WhereIf(!input.EmailAddress.IsNullOrWhiteSpace(), t => t.EmailAddress.Contains(input.EmailAddress)) //如需要精確匹配則用Equals .WhereIf(input.IsActive.HasValue, t => t.IsActive == input.IsActive) //如需要精確匹配則用Equals .WhereIf(input.IsEmailConfirmed.HasValue, t => t.IsEmailConfirmed == input.IsEmailConfirmed) //如需要精確匹配則用Equals .WhereIf(input.IsPhoneNumberConfirmed.HasValue, t => t.IsPhoneNumberConfirmed == input.IsPhoneNumberConfirmed) //如需要精確匹配則用Equals .WhereIf(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //如需要精確匹配則用Equals .WhereIf(!input.PhoneNumber.IsNullOrWhiteSpace(), t => t.PhoneNumber.Contains(input.PhoneNumber)) //如需要精確匹配則用Equals .WhereIf(!input.Surname.IsNullOrWhiteSpace(), t => t.Surname.Contains(input.Surname)) //如需要精確匹配則用Equals .WhereIf(!input.UserName.IsNullOrWhiteSpace(), t => t.UserName.Contains(input.UserName)) //如需要精確匹配則用Equals .WhereIf(!input.UserNameOrEmailAddress.IsNullOrWhiteSpace(), t => t.UserName.Contains(input.UserNameOrEmailAddress) || t.EmailAddress.Contains(input.UserNameOrEmailAddress) || t.FullName.Contains(input.UserNameOrEmailAddress)) //創建日期區間查詢 .WhereIf(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value) .WhereIf(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value); }
可以看出會根據UserPageDto的屬性不同,從而增加更多的處理條件,有的是完全匹配,有些這是模糊匹配,有些如日期則是范圍匹配。
對於數值、日期等有區間范圍的屬性,我們條件的DTO對象中,往往都有一個Start和End的起始值參數的。
這樣我們在利用Vue&Element的前端進行查詢的時候,可以構造對應的區間參數了,如下前端代碼所示。
有時候,為了簡化前端的日期區間代碼,我們可以通過輔助類來簡化處理。
而自定義排序的處理,則可以根據實際的需要進行排序處理,對於自增長的ID類型,使用ID倒序顯示倒是問題不大,而如果是字符串類型,本身是GUID的類型,那么使用ID類排序這是沒有任何意義的,因此必須通過重寫基類函數的方式實現邏輯重寫。
/// <summary> /// 自定義排序處理 /// </summary> /// <param name="query"></param> /// <param name="input"></param> /// <returns></returns> protected override IQueryable<DictData> ApplySorting(IQueryable<DictData> query, DictDataPagedDto input) { //先按字典類型排序,然后同一個字典類型下的再按Seq排序 return base.ApplySorting(query, input).OrderBy(s=>s.DictType_ID).ThenBy(s => s.Seq); }
具體情況根據ID的特點或者排序的具體情況進行排序即可。
最后一項是分頁的處理,則可以按標准的方式處理,默認可以不重寫。
這樣我們前面提到的幾個函數的邏輯,我們根據實際情況重寫部分邏輯即可,從而非常彈性化的實現了條件的處理,排序的處理,分頁的處理等規則。
public virtual async Task<PagedResultDto<TEntityDto>> GetAllAsync(TGetAllInput input) { //判斷權限 CheckGetAllPermission(); //獲取分頁查詢的條件 var query = CreateFilteredQuery(input); //根據條件獲取所有記錄數 var totalCount = await AsyncQueryableExecuter.CountAsync(query); //對查詢內容排序和分頁 query = ApplySorting(query, input); query = ApplyPaging(query, input); //返回領域實體對象 var entities = await AsyncQueryableExecuter.ToListAsync(query); //構造返回結果集,並轉換實體類為DTO對應 return new PagedResultDto<TEntityDto>( totalCount, entities.Select(MapToEntityDto).ToList() ); }
因此,不管是Winform端,或者Vue&Element的BS前端,都可以通過不同的條件信息進行快速的查詢排序處理了。
菜單資源管理的列表界面界面如下所示
用戶列表包括分頁查詢及列表展示、以及可以利用按鈕進行新增、編輯、查看用戶記錄,或者對指定用戶進行重置密碼操作。