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

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

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

ABP开发框架中分页查询排序的实现处理的更多相关文章
- Mysql中分页查询两个方法比较
mysql中分页查询有两种方式, 一种是使用COUNT(*)的方式,具体代码如下 1 2 3 SELECT COUNT(*) FROM foo WHERE b = 1; SELECT a FROM ...
- Oracle中分页查询语句
Oracle分页查询语句使我们最常用的语句之一,下面就为您介绍的Oracle分页查询语句的用法,如果您对此方面感兴趣的话,不妨一看. Oracle分页查询语句基本上可以按照本文给出的格式来进行套用.O ...
- Oracle分页查询排序数据重复问题
参考资料: http://docs.oracle.com/database/122/SQLRF/ROWNUM-Pseudocolumn.htm#SQLRF00255 http://blog.csdn. ...
- Oracle中分页查询和联表查询
1.使用ROWNUM伪列查询 1.1.查询十条数据(rownum<=n) SELECT ROWNUM,A.* FROM v_sjjx_unit_info A WHERE ROWNUM<=1 ...
- mybatis中分页查询
1 如果在查询方法中有多个参数,可以使用map对象将所有数据都存储进去.比如分页查询,需要用到两个参数,可以将这两个参数包装到map中. 例子:分页查询 dao层方法 public List<S ...
- Oracle中分页查询语句的写法
要动态的变化分页查询的条件,比如pageNow 这个变量表示的是当前是第几页, oracle分页有通用写法,假设一页5行 select * from ( select t.*,rownum rn fr ...
- abp CrudAppService 自定义分页、排序
public class GetAllTasksInput : PagedAndSortedResultRequestDto { public TaskState? State { get; set; ...
- Oracle使用MyBatis中RowBounds实现分页查询
Oracle中分页查询因为存在伪列rownum,sql语句写起来较为复杂,现在介绍一种通过使用MyBatis中的RowBounds进行分页查询,非常方便. 使用MyBatis中的RowBounds进行 ...
- Mysql系列(五)—— 分页查询及问题优化
一.用法 在Mysql中分页查询使用关键字limit.limit的语法如下: SELECT * FROM tbl LIMIT 5,10; # Retrieve rows 6-15 limit关键字带有 ...
随机推荐
- Loj#6247-九个太阳【单位根反演】
正题 题目链接:https://loj.ac/p/6247 题目大意 给出\(n,k\)求 \[\sum_{0\leq i\leq n,i|k}\binom{n}{i} \] 对\(998244353 ...
- Python如何连接Mysql及基本操作
什么要做python连接mysql,一般是解决什么问题的 做自动化测试时候,注册了一个新用户,产生了多余的数据,下次同一个账号就无法注册了,这种情况怎么办呢?自动化测试都有数据准备和数据清理的操作,如 ...
- fastjson将json转为Map<String,String>踩坑
字符串 对于一个json字符串 String str = "{"specItem":"[红, 大]","specName":&qu ...
- 后缀自动机(SAM)奶妈式教程
后缀自动机(SAM) 为了方便,我们做出如下约定: "后缀自动机" (Suffix Automaton) 在后文中简称为 SAM . 记 \(|S|\) 为字符串 \(S\) 的长 ...
- Bloom Filter算法
Bloom Filter算法详解 什么是布隆过滤器 布隆过滤器(Bloom Filter)是 1970 年由布隆提出的.它实际上是一个很长的二进制向量和一系列随机映射函数 (下面详细说),实际上你也可 ...
- I-Base62
I - Base62 PS:一个任意进制转换的大数问题 传送门:Base62 短除法原理: 20(10进制) => 202(3进制) 20 = (2 * 3 ^ 2 + 0 * 3 ^ 1 + ...
- 性能利器 Takin 来了!首个生产环境全链路压测平台正式开源
6 月 25 日,国内知名的系统高可用专家数列科技宣布开源旗下核心产品能力,对外开放生产全链路压测平台产品的源代码,并正式命名为 Takin. 目前中国人寿.顺丰科技.希音.中通快递.中国移动.永辉超 ...
- [no code][scrum meeting] Alpha 8
项目 内容 会议时间 2020-04-14 会议主题 API文档第一版交付 会议时长 30min 参会人员 PM+OCR组成员 $( "#cnblogs_post_body" ). ...
- $dy$讲课总结
字符串: 1.广义后缀自动机(大小为\(m\))上跑一个长度为\(n\)的串,所有匹配位置及在\(parent\)树上其祖先的数量的和为\(min(n^2,m)\),单次最劣是\(O(m)\). 但是 ...
- 8.18考试总结[NOIP模拟43]
又挂了$80$ 好气哦,但要保持优雅.(草 T1 地衣体 小小的贪心:每次肯定从深度较小的点向深度较大的点转移更优. 模拟一下,把边按链接点的子树最大深度排序,发现实际上只有上一个遍历到的点是对当前考 ...