【问题标题】:ASP.NET MVC2 LINQ - Repository pattern, where should the pagination code go?ASP.NET MVC2 LINQ - 存储库模式,分页代码应该去哪里?
【发布时间】:2011-06-22 22:13:04
【问题描述】:

我正在努力为分页添加 HtmlHelper,但从性能和可维护性的角度来看,我不确定将分页代码的某些部分放在哪里合适和/或最有益的位置。

我不确定 Linq to SQL 数据操作的 Skip()、Take() 和 Count() 部分是否应该存在于存储库或控制器中。

我也不确定它们的顺序和使用位置是否会以任何方式影响性能。

根据我的理解,如果它们位于存储库中,那么它的工作方式是:
1. 我会将 pageIndex 和 pageSize 作为参数传递给存储库的方法,该方法从
2. 然后从数据库中获取完整的数据集。
3. 然后将该完整数据集的 TotalItems 计数存储在一个变量中.
4. 然后应用 Skip() 和 Take() 使数据集只保留我需要的页面。
5. 显示部分数据在视图中设置为单个页面。

根据我的理解,如果他们住在控制器中,它将是这样工作的: 1. 我将从存储库中获取完整的数据集并将其存储到控制器内部的变量中。 2. 然后获取完整数据集的 TotalItems 计数。 3. 然后应用 Skip() 和 Take() 使数据集只保留我需要的页面。 4. 在视图中将部分数据集显示为单个页面。

在控制器内部(我意识到我会在此处错误地获取页数而不是 TotalItems):


Character[] charactersToShow = charactersRepository.GetCharactersByRank(this.PageIndex, this.PageSize);
RankViewModel viewModel = new RankViewModel
{
    Characters = charactersToShow,
    PaginationInfo = new PaginationInfo
    {
        CurrentPage = this.PageIndex,
        ItemsPerPage = this.PageSize,
        TotalItems = charactersToShow.Count()
    }
};

存储库内部:


public Character[] GetCharactersByRank(int PageIndex, int PageSize)
{
    IQueryable characters = (from c in db.Characters
        orderby c.Kill descending
        select new Character {
            CharID = c.CharID,
            CharName = c.CharName,
            Level = c.Level
        });
    characters = PageIndex > 1 ? characters.Skip((PageIndex - 1) * PageSize).Take(PageSize) : characters.Take(PageSize);
    return characters.ToArray();
}

此代码是我如何实现存储库中的 Skip()、Take() 和 Count() 代码的部分示例。我实际上并没有实现获取和返回 TotalItems,因为那时我意识到我不知道放置它的正确位置。

我不确定将这些放在哪里的部分原因是我不知道 Linq to SQL 在底层是如何工作的,因此我不知道如何优化性能。我也不知道在这种情况下这是否是一个问题。

当您在 Linq to SQL 上执行 .Count() 时,它是否必须从数据库中获取所有记录? 如果我执行 .Count(),然后执行 .Skip() 和 .Take(),是否必须进行单独的查询? 在 .Skip() 和 .Take() 之前使用 .Count() 是否存在性能问题?

这是我第一次使用 ORM,所以我不确定会发生什么。我知道我可以查看 Linq to SQL 正在运行的查询,但是我觉得在这种情况下听听有经验的人会更好地利用我的时间。

我想更深入地了解这一点,任何见解将不胜感激。

【问题讨论】:

  • 回答 一个 您的问题,是的 - 它确实需要进行 2 次 DB 调用。避免这种情况的唯一方法是在数据库本身中进行 100% 的分页。

标签: asp.net linq-to-sql asp.net-mvc-2 repository


【解决方案1】:

我在我的Helpers 文件夹中保留了一个通用的PaginatedList 类,我还放了其他Helper 类。

PaginatedList 直接来自 NerdDinner,看起来像这样。

public class PaginatedList<T>: List<T>
{
    public int PageIndex { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }
    public int TotalPages { get; private set; }

    public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
    {
        PageIndex = pageIndex;
        PageSize = pageSize;
        TotalCount = source.Count();
        TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize);

        this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
    }

    public bool HasPreviousPage
    {
        get
        {
            return (PageIndex > 0);
        }
    }

    public bool HasNextPage
    {
        get
        {
            return (PageIndex + 1 < TotalPages);
        }
    }
}

【讨论】:

  • 这个问题是每次都会触发 2 个查询 - 一个用于 .Count(),另一个用于 Skip/Take 组合。但是,我还没有找到避免这种情况的方法。只有其他选择是进行分页数据库端。
  • 我在 NerdDinner 网站上找到了我想要阅读的内容,感谢您的参考 :)
【解决方案2】:

我在上面 Marko 提到的 NerdDinner 网站上找到了这个,它回答了我的很多问题。

来自第 8 页底部的 NerdDinner

IQueryable 是一个非常强大的功能,它支持各种有趣的延迟执行场景(如基于分页和组合的查询)。与所有强大的功能一样,您要小心使用它并确保它不被滥用。
重要的是要认识到从存储库返回 IQueryable 结果使调用代码能够将链式运算符方法附加到它,从而参与最终的查询执行。如果您不想为调用代码提供这种能力,那么您应该返回 IList 或 IEnumerable 结果 - 其中包含已执行的查询的结果。
对于分页场景,这需要您将实际的数据分页逻辑推送到被调用的存储库方法中。在这种情况下,我们可能会更新 FindUpcomingDinners() finder 方法,使其具有返回 PaginatedList 的签名:
PaginatedList FindUpcomingDinners(int pageIndex, int pageSize) { }
或者返回一个 IList,并使用“totalCount”输出参数返回晚餐的总数:
IList FindUpcomingDinners(int pageIndex, int pageSize, out int totalCount) { }

【讨论】:

    猜你喜欢
    • 2012-06-04
    • 2018-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    相关资源
    最近更新 更多