【问题标题】:Who's responsibility should be to paginate controller/domail service/repository?谁应该负责为控制器/domail 服务/存储库分页?
【发布时间】:2017-02-13 09:52:44
【问题描述】:

我的问题对于专业人士来说可能看起来很奇怪,但请注意我来自 ruby​​ on rails world =)

所以,我正在学习 ASP.NET Core。与rails相比,我喜欢我在其中看到的东西。但是总有这样的,但是...让我描述一下理论问题。

假设我有一个Product 模型。数据库中有超过 9000 条记录。很明显,我必须对它们进行分页。我已经阅读了this 文章,但在我看来这里有些问题,因为控制器不应该直接使用context。它必须使用一些存储库(但为了简单起见,可能会以这种方式提供该示例)。

所以我的问题是:谁应该负责分页?控制器应该从存储库接收一些可查询的对象并只获取它需要的那些记录吗?还是应该是我自己的业务服务做同样的事情?或者存储库是否应该有类似public IEnumerable<Product> ListProducts(int offset, int page) 的方法?

【问题讨论】:

  • 如果您不返回可查询列表,那么存储库看起来肯定是个好地方。也许您可以添加页面和 pageSize 可以由控制器或业务服务上的某些选项处理:-)
  • 这取决于您的架构。如果你从你的存储库返回IQuariable<T> - 你真的可以在任何你想要的地方进行分页。如果没有 - 最好让您的分页尽可能靠近您的数据库。
  • @teovankot 从存储库返回 IQueryable<T> 是一个好/可接受的主意吗?
  • 当然。 Linq 旨在简化您的生活,而不是不使用它。还要意识到,只要您的数据库层支持 linq(以将其转换为 SQL 而不仅仅是 linq-to-already-loaded-objects 的形式),那么 LINQ is 实际上是一种抽象层。他们通常告诉不要直接使用上下文的原因是因为您希望您的控制器独立于存储机制。例如,如果微软停止了实体框架并且你需要使用其他东西,如果与 EF 相关的代码没有分散在所有代码中,那么重构的范围就会更小
  • hmm... 看来,从存储库返回 IQueryable<T> 不是一个好主意,如 herehere 所说。还有更多的例子,但这些已经足够了

标签: asp.net asp.net-mvc asp.net-core asp.net-core-mvc


【解决方案1】:

解决此问题的一个领域驱动设计方案是使用Specification。规范设计模式描述对象中的查询。因此,您可以创建一个 PagedProduct 规范,该规范将采用任何必要的参数(pageSize、pageNumber、filter)。然后,您的存储库方法之一(通常是 List() 重载)将接受 ISpecification 并且能够在给定规范的情况下产生预期的结果。这种方法有几个好处。该规范有一个名称(而不仅仅是一堆 LINQ),您可以对其进行推理和讨论。它可以单独进行单元测试以确保正确性。如果您需要相同的行为(例如在 MVC 视图操作和 Web API 操作上),它可以很容易地重复使用。

我在Pluralsight Design Patterns Library 中介绍了规范模式。

【讨论】:

  • 感谢您的回复。我看过你提供的视频。在此之前我已经阅读过规范。但是即使在视频中您说此模式用于过滤数据,您也不能像使用其他规范一样仅使用分页规范(它不会传递给Where 方法)。并且需要分别通过通用规范和分页规范
  • 这取决于你如何实现它。您当然可以在规范中包含分页/排序,只要您以一种可以使用此信息来构建其查询的方式编写存储库。
  • 另外,我现在在我的 Specification nuget 包中进行了分页:nuget.org/packages/Ardalis.Specification
【解决方案2】:

首先,我想提醒您,您链接的所有此类示例都过于简化,因此不应驱使您相信这是正确的方法。具有较少抽象层的简单事物更易于监督和理解(至少对于初学者的简单示例,当读者可能不知道在哪里寻找什么时),这就是它们以这样的方式呈现的原因。

关于这个问题:以上都不是。如果我必须在它们之间做出决定,那么我会说服务和/或存储库,但这取决于您如何定义存储层等。

“以上都不是”,然后呢?我的偏好是在服务层和 Web UI 层之间实现一个中间层。服务层公开操作功能,但对于读取操作,将整个集合公开为 IQueryablenot 作为 IEnumerable,以便您可以利用 LINQ-to-whatever-storage。

我为什么要这样做,很多人可能会问。因为几乎所有时候你都会使用专门的视图模型。例如,要在管理页面上显示产品列表,您需要在 products 表中显示列的值,但您很可能还需要显示其类别。在极少数情况下,您只需要一个表中的数据,并且通过将项目公开为 IQueryable<T>,您可以获得能够像这样执行 Selects 的好处:

public IEnumerable<ProductAdminTableViewModel> GetProducts(int page, int pageSize)
{
    backingQueryable.Select(prod => new ProductAdminTableViewModel
    {
        Id = prod.Id,
        Category = prod.Category.Name, // your provider will likely resolve this to a Join
        Name = prod.Name
    }).Skip((page - 1) * pageSize).Take(pageSize).ToList();
}

正如评论的那样,通过将后备存储用作IQueryable,您将能够在查询到达数据库之前进行预测,因此您可以避免任何讨厌的 Select N+1s。

它位于中间层的原因只是您不想在您的存储库和服务层(项目)中添加对 Web 项目的引用,但因此您无法在您的服务层仅仅是因为无法在那里解析视图模型。这意味着视图模型也驻留在同一个项目中,为此,MVC 项目仅包含视图、控制器和应用程序的 ASP.NET MVC 相关的内脏。我通常将此中间层称为“SolutionName.Web.Core”,它引用服务层以便能够访问IQueryable&lt;T&gt;-returning 方法。

【讨论】:

    猜你喜欢
    • 2015-10-08
    • 2011-10-02
    • 2011-04-13
    • 1970-01-01
    • 2015-05-07
    • 2010-12-06
    • 2023-02-22
    • 1970-01-01
    • 2011-06-11
    相关资源
    最近更新 更多