ABP开发框架中分页查询排序的实现处理

2021年11月23日 阅读数:3
这篇文章主要向大家介绍ABP开发框架中分页查询排序的实现处理,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

在ABP开发框架中应用服务层ApplicationService类中,都会提供常见的一些如GetAll、Get、Create、Update、Delete等的标准处理接口,而因为在ApplicationService类定义的时候,都会传入几个不一样的类型做为泛型的参数,实现强类型的类型处理,本篇随笔对于分页查询排序的实现处理作一个详细的介绍,介绍其中对分页查询条件的定义,子类应用服务层的条件查询逻辑重写、排序逻辑重写等规则的处理。前端

一、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的领域对象,它的定义以下所示app

    public class User : AbpUser<User>

因为User须要集成AbpUser基类的一些特性,所以有继承关系,它主要就是负责和数据库模型打交道的对象。框架

而若是不是相似User这样系统用到的基类对象,那么咱们就须要以下定义,指定表单的名称,以及对象的约束条件了,以下字典的领域对象以下定义所示。async

而 MyAsyncServiceBase 的第二个参数则是用于传递的DTO对象,能够认为它和数据库没有直接的关系,不过因为引入了AutoMapper,咱们通常看它们的属性仍是有不少相同的地方,不过DTO更加面向的是业务界面,而非存储处理。ide

若是对于一些界面特殊的数据信息,须要转换为领域对象的属性,则须要进行特别的自定义映射处理了。函数

如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; }
    }

 

二、分页查询排序的实现处理

 前面咱们介绍了应用服务层中利用泛型基类的参数定义,能够强类型返回各项不一样数据接口,这种就是很是弹性化的设计模式了。

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前端,均可以经过不一样的条件信息进行快速的查询排序处理了。

 菜单资源管理的列表界面界面以下所示

用户列表包括分页查询及列表展现、以及能够利用按钮进行新增、编辑、查看用户记录,或者对指定用户进行重置密码操做。