.Net 分页功能实现
在开发一个项目过程中,出现需要对搜索结果进行分页的需求,实现过程记录如下
1. 首先我们需要有一个分页类PaginatedList<TEntity>, 这个分页类大概包括以下信息
总行数,当前页面,每页行数,总页数, 前一页,下一页. 为了方便后面操作,我们使它继承自List<T>
写成如下:
public class PaginatedList<TEntity> : List<TEntity> { public int PageIndex { get; private set; } public int PageSize { get; private set; } public int TotalCount { get; private set; } public int TotalPageCount { get; private set; } public bool HasPreviousPage => (PageIndex > 1); public bool HasNextPage { get { return (PageIndex < TotalPageCount); } } public PaginatedList() { } public PaginatedList(IEnumerable<TEntity> source, int pageIndex, int pageSize, int totalCount) : this() { if (source == null) { throw new ArgumentNullException("source"); } AddRange(source); PageIndex = pageIndex; PageSize = pageSize; TotalCount = totalCount; TotalPageCount = (int)Math.Ceiling(totalCount / (double)pageSize); } }
整个业务逻辑是这样的,我们在业务逻辑层,会根据一些条件,包括where条件,排序条件去得到一个IQueryable<Entity>. 然后对这个得到的结果IQueryable<Entity>进行分页. 这里面又有2个步骤
2. 从结果集IQueryable<Entity>中得到当前页面的数据. 这个我们可以写一个IQueryable的扩展方法来实现
public static class QueryableExtensions { public static IQueryable<TEntity> PaginateQuery<TEntity>(this IQueryable<TEntity> query, int pageOffset, int pageSize) { var entities = query.Skip(pageOffset).Take(pageSize); return entities; } }
3. 在上一步中得到的当前页面的数据,我们需要得到它的PaginatedList结果集,以便返回给前端使用. 这里还是使用IQueryable的扩展方法来实现,和上面在同一个静态类中
public static class QueryableExtensions {
public static async Task<PaginatedList<TEntity>> ToPaginatedList<TEntity>(this IQueryable<TEntity> query, int pageIndex, int pageSize, int total)
{
var list = await query.ToListAsync();
return new PaginatedList<TEntity>(list, pageIndex, pageSize, total);
}
public static IQueryable<TEntity> PaginateQuery<TEntity>(this IQueryable<TEntity> query, int pageOffset, int pageSize) { var entities = query.Skip(pageOffset).Take(pageSize); return entities; } }
4. 这一步完成后,我们来看数据层,数据层需要做以下工作
4a: 根据where条件,排序条件,include属性等获取需要的数据IQueryable<TEntity>集
4b: 对得到的IQueryable<TEntity>结果集,调用上面的扩展方法来得到前端需要的PaginatedList数据集
数据层代码写在EntityRepository里面,代码如下
public class EntityRepository<TEntity> : IRepository<TEntity> where TEntity : Class { private IDbSet<TEntity> _dbEntitySet; private readonly IDbContext _context; public EntityRepository(IRepoUnitOfWork unitOfWork /* , int userId*/) { _context = unitOfWork.DbContext; _dbEntitySet = _context.Set<TEntity>(); } private IDbSet<TEntity> Entities { get { return _dbEntitySet ?? (_dbEntitySet = _context.Set<TEntity>()); } } public void Dispose() { //_context.Dispose(); } public async Task<PaginatedList<TEntity>> GetAll<TKey>(int pageIndex, int pageSize, Expression<Func<TEntity, TKey>> keySelector, Expression<Func<TEntity, bool>> predicate, OrderBy orderBy, params Expression<Func<TEntity, object>>[] includeProperties) { var entities = FilterQuery(keySelector, predicate, orderBy, includeProperties); var total = entities.Count();// entities.Count() is different than pageSize entities = entities.Paginate(pageIndex, pageSize); //调用上面的扩展方法 return await entities.ToPaginatedList(pageIndex, pageSize, total); //调用上面的扩展方法 } private IQueryable<TEntity> FilterQuery<TKey>(Expression<Func<TEntity, TKey>> keySelector, Expression<Func<TEntity, bool>> predicate, OrderBy orderBy, Expression<Func<TEntity, object>>[] includeProperties) { var entities = IncludeProperties(includeProperties); entities = predicate != null ? entities.Where(predicate) : entities; entities = orderBy == OrderBy.Ascending ? entities.OrderBy(keySelector) : entities.OrderByDescending(keySelector); return entities; } private IQueryable<TEntity> IncludeProperties(params Expression<Func<TEntity, object>>[] includeProperties) { IQueryable<TEntity> entities = _dbEntitySet; foreach (var includeProperty in includeProperties) { entities = entities.Include(includeProperty); } return entities; } }
在上面的FilterQuery方法中,我们看到有个参数类型是OrderBy, 这个是我自己定义的Enum类型,代码如下
public enum OrderBy { Ascending, Descending }
就是排序使用
最后,我们来看前面业务层使用的例子:
var people = await _personRepository.GetAll(0, 10, i => i.PersonId, p => p.PersonCircles.Any(a => !a.IsRemoved && a.PersonId == id), OrderBy.Ascending, x => x.PersonCircles, x => x.Image, x => x.PersonCircles.Select(s => s.Person), x => x.PersonCircles.Select(s => s.Posts.Select(i => i.Image)));