首页 > 解决方案 > 使用 IList IEnumerable 存储库实现分页列表

问题描述

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

当前存储库如下。它们返回任务列表、IEnumerables 等。

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

如果我实现以下分页列表答案,则存储库返回 IList,而不是 IQueryable。所以答案仍然是首先检索所有数据(导致性能问题),然后过滤它。

https://stackoverflow.com/a/5036023/12425844

存储库:

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 Entity Framework Repository Pattern 为什么不返回 Iqueryable?

标签: c#.netlinq.net-corerepository

解决方案


如果您想准确返回页面的数据,您可以使用 LINQ .Skip 和 .Take,它仍然会返回 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;
        }

然后根据需要添加使用可查询的方式(使用扩展方法的示例,但可以使用要包装的类来完成,或者在您的存储库中使用 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 的不同使用允许返回查询以通过分页对其进行细化

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


推荐阅读