使用 IList IEnumerable Repository 实现分页列表

Implement Paged List with IList IEnumerable Repository

我继承了一个项目,并要求在我们所有的存储库(500 多种方法)中实现分页,以便在 Web 前端仅显示数据段。

当前存储库如下。他们 return 任务列表、IEnumerables 等。

我们将如何完成并为以下存储库示例实现分页?

如果我实施以下分页列表答案,回购 return IList,而不是 IQueryable。所以答案,仍然会先检索所有数据(导致性能问题),然后过滤它。

存储库:

public Task<List<Product>> GetProductListByProductNumber(int bkProductNumber)
{
    var productData = _context.Product
        .Include(c => c.LkProducttype)
        .Where(c => c.BkProductnumber == bkProductNumber).ToListAsync();
    return productData ;
}


public Task<List<Product>> GetProductListByProductDescription(string productDescription)
{
    var productData = _context.Product
        .Include(c => c.LkProducttype)
        .Where(c => c.ProductDescription == productDescription).ToListAsync();
    return productData;
}

页面列表答案

public PagedList<T> Find(Expression<Func<T,bool>> predicate, int pageNumber, pageSize)
{
   return repository
             .Find()
             .Where(predicate)
             .ToPagedList(pageNumber, pageSize);
}

*寻找一种有效的方法,因此不必重新复制和粘贴所有 500 多个带分页的方法,如果可能的话

如果将所有 Repos 方法更改为 IQueryable,可能会解决问题。但是,正在阅读 Repos should Not return IQueryable per article here

如果您想要return 一个页面的准确数据,您可以使用 LINQ .Skip 和 .Take,那仍然 return 一个 IQueryable

public Task<List<Product>> GetProductListByProductNumberAsync(int bkProductNumber, int pageNumber, int pageSize)
{
    var productData = _context.Product
        .Include(c => c.LkProducttype)
        .Where(c => c.BkProductnumber == bkProductNumber)
        .Skip(pageSize * (pageNumber - 1))
        .Take(pageSize)
        .ToListAsync();
    return productData;
}

此方法的默认设置是,如果定期出现新数据,您的页面可能会失去同步。比如你查询第2页,25条,得到25-49条。然后插入2个新项目,你查询第3页,你会得到原来的48-72 但这是大多数网站的标准行为。

如果您想保留现有查询,现在要解决 DRY 问题,我会首先将查询本身与到异步列表的转换分开:

  private IQueryable<Product> GetProductListByProductNumberQuery(int bkProductNumber)
        {
            var productData = _context.Product
                .Include(c => c.LkProducttype)
                .Where(c => c.BkProductnumber == bkProductNumber);
            return productData;
        }

然后根据需要添加使用可查询的方式(使用扩展方法的示例,但可以使用 class 包装来完成,或者在您的存储库中使用 2 种调用可查询的方式):

  public static class QueryableExtensions
    {
        public static Task<List<T>> ToTask<T>(this IQueryable<T> query) 
        {
            return query.ToListAsync(); // might as well call directly .ToListAsync
        }

        public static Task<List<T>> ToTaskPaged<T>(this IQueryable<T> query, int pageNumber, int pageSize)
        {
            return query
                .Skip(pageSize * (pageNumber-1))
                .Take(pageSize)
                .ToListAsync();
        }
    }

不同的 ToListAsync 使用允许返回到查询以使用分页优化它

查询是 public 还是私人查询取决于您希望如何使用它们。如果您认为存储库负责提供查询而不是如何 运行 它们,那么查询将是 public。如果您希望您的存储库仅公开包装的已执行查询,那么这些查询将是私有的。 好的做法是不要 return IQueryable,但这会 运行 违反您设置的约束,即您不想为每个查询创建 2 个版本(分页,不分页),所以有了这个约束我会去公开 IQueryable。但是你 link 的文章警告用户可以开始用它做疯狂的事情是正确的。如果它只暴露给你的团队,我认为只要每个人都意识到这是一种技术债务就可以了。 如果它在 public API 中,那么您以后无法修复它。