分页与列表数据
Aegis 在 FreeSql 的 ISelect<T> 上扩展了 ToPagedList / ToPagedListAsync 方法。分页涉及两个层的数据结构:
- Repository 层:
PagedList<T>— 泛型参数是 Entity - Service 层:
PagedResult<TDto>— 泛型参数是 Dto
通过 DtoPaged<TDto>.Convert 在两层之间转换。
Repository 层分页
单表分页
在 FreeSql 查询表达式后调用 ToPagedListAsync:
public async Task<PagedList<OrderEntity>> GetPagedOrdersAsync(OrderQuery query)
{
return await this
.WhereIf(!string.IsNullOrEmpty(query.Keyword), x => x.OrderNo.Contains(query.Keyword))
.WhereIf(query.Status.HasValue, x => x.Status == query.Status)
.OrderByDescending(x => x.CreateTime)
.ToPagedListAsync(query.PageIndex, query.PageSize);
}
分页查询一定要加 OrderBy,否则结果顺序不稳定。
多表联查分页
多表联查分页支持最多 8 张表。使用 Select<T1, T2, ...> 指定参与联查的实体,在 ToPagedListAsync 中传入映射表达式:
public async Task<PagedList<OrderListMapping>> GetOrderListAsync(OrderQuery query)
{
return await this.Orm
.Select<OrderEntity, CustomerEntity>()
.LeftJoin((order, customer) => order.CustomerId == customer.Id)
.WhereIf(!string.IsNullOrEmpty(query.Keyword),
(order, customer) => order.OrderNo.Contains(query.Keyword))
.OrderByDescending((order, customer) => order.CreateTime)
.ToPagedListAsync(
(order, customer) => new OrderListMapping
{
OrderId = order.Id,
OrderNo = order.OrderNo,
CustomerName = customer.Name
},
query.PageIndex,
query.PageSize);
}
自动匹配字段
多表联查时,如果映射对象的字段名和实体属性名一致,可以用空对象初始化器让 FreeSql 自动匹配:
.ToPagedListAsync(
(order, customer) => new OrderListMapping(),
query.PageIndex,
query.PageSize);
PagedList 结构
public readonly record struct PagedList<T>
{
public List<T> Entities { get; init; } // 当前页数据
public long Total { get; init; } // 总记录数
public int CurrentIndex { get; init; } // 当前页码
public int PageSize { get; init; } // 每页条数
}
PagedList 是不可变结构体,只作为返回值使用,不要作为方法入参。
Service 层分页转换
PagedList<Entity> 不能直接返回给 Controller。使用 DtoPaged<TDto>.Convert 将 Entity 分页结果映射为 Dto 分页结果:
public async Task<PagedResult<OrderDto>> GetOrderListAsync(OrderQuery query)
{
var pagedList = await _orderRepository.GetPagedOrdersAsync(query);
return DtoPaged<OrderDto>.Convert<OrderEntity>(pagedList);
}
Convert 内部使用 Mapster 的 .Adapt<T>() 完成映射。
不需要映射时
如果 Entity 和 Dto 完全一致,或者不需要映射,传入 false 跳过:
return DtoPaged<OrderDto>.Convert<OrderEntity>(pagedList, isMapping: false);
最佳实践
分页参数从前端传入
分页参数(PageIndex、PageSize)放在 Request 对象中由前端传入,不要在 Service 或 Repository 中硬编码。
Repository 返回 PagedList,Service 返回 PagedResult
- Repository 方法返回
PagedList<Entity> - Service 方法返回
PagedResult<TDto> - Controller 拿到
PagedResult<TDto>后包装为EntitiesResponse<TDto>返回
这个分层保证了 Entity 不泄漏到 Controller 层。
加 OrderBy
没有 OrderBy 的分页查询,不同页之间可能出现重复数据或遗漏数据。始终在分页前指定排序。
大数据量场景注意性能
ToPagedListAsync 会先执行 Count 查询再执行 ToList 查询。数据量特别大时,考虑是否真的需要总数。如果不需要总数,直接用 ToListAsync 替代。
边界与限制
- 多表联查分页最多支持 8 张表
PagedList是 struct,不建议作为方法参数传递DtoPaged<TDto>.Convert依赖 Mapster 的.Adapt<T>(),属性名不一致时需要配置映射规则