在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开发框架中分页查询排序的实现处理的更多相关文章

  1. Mysql中分页查询两个方法比较

    mysql中分页查询有两种方式, 一种是使用COUNT(*)的方式,具体代码如下 1 2 3 SELECT COUNT(*) FROM foo WHERE b = 1;   SELECT a FROM ...

  2. Oracle中分页查询语句

    Oracle分页查询语句使我们最常用的语句之一,下面就为您介绍的Oracle分页查询语句的用法,如果您对此方面感兴趣的话,不妨一看. Oracle分页查询语句基本上可以按照本文给出的格式来进行套用.O ...

  3. Oracle分页查询排序数据重复问题

    参考资料: http://docs.oracle.com/database/122/SQLRF/ROWNUM-Pseudocolumn.htm#SQLRF00255 http://blog.csdn. ...

  4. Oracle中分页查询和联表查询

    1.使用ROWNUM伪列查询 1.1.查询十条数据(rownum<=n) SELECT ROWNUM,A.* FROM v_sjjx_unit_info A WHERE ROWNUM<=1 ...

  5. mybatis中分页查询

    1 如果在查询方法中有多个参数,可以使用map对象将所有数据都存储进去.比如分页查询,需要用到两个参数,可以将这两个参数包装到map中. 例子:分页查询 dao层方法 public List<S ...

  6. Oracle中分页查询语句的写法

    要动态的变化分页查询的条件,比如pageNow 这个变量表示的是当前是第几页, oracle分页有通用写法,假设一页5行 select * from ( select t.*,rownum rn fr ...

  7. abp CrudAppService 自定义分页、排序

    public class GetAllTasksInput : PagedAndSortedResultRequestDto { public TaskState? State { get; set; ...

  8. Oracle使用MyBatis中RowBounds实现分页查询

    Oracle中分页查询因为存在伪列rownum,sql语句写起来较为复杂,现在介绍一种通过使用MyBatis中的RowBounds进行分页查询,非常方便. 使用MyBatis中的RowBounds进行 ...

  9. Mysql系列(五)—— 分页查询及问题优化

    一.用法 在Mysql中分页查询使用关键字limit.limit的语法如下: SELECT * FROM tbl LIMIT 5,10; # Retrieve rows 6-15 limit关键字带有 ...

随机推荐

  1. Redis之品鉴之旅(五)

    Redis事务 原子性:就是最小的单位 一致性:好多命令,要么全部执行成功,要么全部执行失败 隔离性:一个会话和另一个会话之间是互相隔离的 持久性:执行了就执行了,数据保存在硬盘上 典型例子:银行转账 ...

  2. Excel 高亮当前行、高亮重复行的探索

    本文原创,转载请注明出处:https://www.cnblogs.com/wotent/p/15348891.html TLDR 下载文件 高亮.zip ,将解压后的"高亮.xlam&quo ...

  3. css 弹性盒子--“垂直居中”--兼容写法

    使用弹性盒子兼容低端适配有时需要: display:flex 和 display:-webkit-box   display: -webkit-box; -webkit-box-align: cent ...

  4. JavaWeb#JSP内置对象

    [1.JSP内置对象简介] 内置对象:不加声明就可以在JSP页面脚本中使用的成员变量.(使用这些对象可以更容易收集客户端发送的请求信息,响应客户端的请求及存储客户信息.) 主要介绍:out,reque ...

  5. Win32窗口框架

    Win32窗口框架 WindowClass 单例,负责窗口初始化注册和取消注册: 负责提供静态方法: 放在Window类内部,方便初始化时,wndProc(HandleMsgSetup)的赋值: cl ...

  6. ApacheCon 首次亚洲大会火热来袭,SphereEx 邀您共赴年度盛会!

    ApacheCon 是 Apache 软件基金会(ASF)的官方全球系列大会.作为久负盛名的开源盛宴,ApacheCon 在开源界备受关注,也是开源运动早期的知名活动之一. ApacheCon 每年举 ...

  7. 题解「BZOJ4310」跳蚤

    题目传送门 Description 现在有一个长度为 \(n\) 的字符串,将其划分为 \(k\) 段,使得这 \(k\) 段每一段的字典序最大子串中字典序最大的字符串字典序尽量小.求出这个字符串. ...

  8. SoapUI入门实例

    一.Soapui介绍 WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述Web Service及其函数.参数和返回值.它是WebSer ...

  9. Python 面向对象笔记

    Python 面向对象课程笔记 前言 Python 面向对象 正文 基本概念 什么是对象: 万物皆对象 对象是具体物体: 拥有属性 拥有行为 封装零散为整体 OOP(Object Oriented P ...

  10. 第五课第四周笔记2:Self-Attention 自注意力

    Self-Attention 自注意力 让我们跳进去谈谈transformer的self-attention机制.如果您能了解本视频背后的主要思想,您就会了解变压器网络工作背后最重要的核心思想. 让我 ...