ABP開發框架中分頁查詢排序的實現處理


在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前端,都可以通過不同的條件信息進行快速的查詢排序處理了。

 菜單資源管理的列表界面界面如下所示

用戶列表包括分頁查詢及列表展示、以及可以利用按鈕進行新增、編輯、查看用戶記錄,或者對指定用戶進行重置密碼操作。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM