跳到主要内容
版本:3.0.0

分页与列表数据

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);

最佳实践

分页参数从前端传入

分页参数(PageIndexPageSize)放在 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>(),属性名不一致时需要配置映射规则