因此,我在应用程序中实现存储库模式,并在我对该模式的理解中遇到了两个“问题”:

  1. 查询 - 我读过有关在使用存储库时不应使用 IQueryable 的回复。但是,很明显您希望每次调用方法时都不会返回完整的对象列表。是否应该实施?如果我有一个名为 List 的 IEnumerable 方法,那么 IQueryable 的一般“最佳实践”是什么?它应该/不应该有哪些参数?

  2. 标量值 - 返回单个标量值而不必返回整个记录的最佳方法是什么(使用存储库模式)?从性能的角度来看,在整行中仅返回单个标量值不是更有效吗?

有帮助吗?

解决方案

严格来说,存储库提供用于获取/放置域对象的集合语义。它提供了围绕物化实现(ORM、手动、模拟)的抽象,以便域对象的使用者与这些细节解耦。在实践中,存储库通常抽象对实体的访问,即具有身份的域对象,并且通常是持久生命周期(在 DDD 风格中,存储库提供对聚合根的访问)。

存储库的最小接口如下:

void Add(T entity);
void Remove(T entity);
T GetById(object id);
IEnumerable<T> Find(Specification spec);

尽管您会看到命名差异以及 Save/SaveOrUpdate 语义的添加,但以上是“纯粹”的想法。您将获得 ICollection 添加/删除成员以及一些查找器。如果您不使用 IQueryable,您还会在存储库中看到查找器方法,例如:

FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();

在这种情况下使用 IQueryable 有两个相关的问题。第一个是可能以域对象关系的形式向客户端泄露实现细节,即违反德米特法则。第二个是存储库获得了查找可能不属于域对象存储库本身的职责,例如,查找与所请求的域对象相关而不是相关数据的投影。

此外,使用 IQueryable “打破”了该模式:具有 IQueryable 的存储库可能会也可能不会提供对“域对象”的访问。IQueryable 为客户端提供了很多关于最终执行查询时将具体化什么的选项。这是有关使用 IQueryable 的争论的主旨。

关于标量值,您不应使用存储库来返回标量值。如果您需要标量,通常可以从实体本身获取。如果这听起来效率低下,确实如此,但您可能不会注意到,具体取决于您的负载特征/要求。如果由于性能原因或需要合并来自多个域对象的数据而需要域对象的备用视图,则有两种选择。

1) 使用实体的存储库查找指定的实体并项目/映射到扁平化视图。

2) 创建一个查找器接口,专门用于返回封装您需要的扁平化视图的新域类型。这不会是一个存储库,因为不会有集合语义,但它可能会在幕后使用现有的存储库。

如果您使用“纯”存储库来访问持久化实体,需要考虑的一件事是您会损害 ORM 的一些好处。在“纯”实现中,客户端无法提供如何使用域对象的上下文,因此您无法告诉存储库:“嘿,我只是要更改客户。名称属性,所以不要打扰那些急切的引用。”另一方面,问题是客户是否应该知道这些东西。这是一把双刃剑。

就使用 IQueryable 而言,大多数人似乎都愿意“打破”模式以获得动态查询组合的好处,特别是对于分页/排序等客户端职责。在这种情况下,您可能会:

Add(T entity);
Remove(T entity);
T GetById(object id);
IQueryable<T> Find();

然后,您可以取消所有这些自定义 Finder 方法,随着查询需求的增长,这些方法确实会使存储库变得混乱。

其他提示

作为对@lordur的回应,我真的不喜欢指定存储库接口的事实上的方法。

因为解决方案中的接口要求每个存储库实现至少需要一个 Add、Remove、GetById 等。现在考虑一个场景,通过存储库的特定实例进行保存没有意义,您仍然必须使用 NotImplementedException 或类似的方法来实现其余方法。

我更喜欢像这样分割我的存储库接口声明:

interface ICanAdd<T>
{
    T Add(T entity);
}

interface ICanRemove<T>
{
    bool Remove(T entity);
}

interface ICanGetById<T>
{
    T Get(int id);
}

SomeClass 实体的特定存储库实现可能如下所示:

interface ISomeRepository
    : ICanAdd<SomeClass>, 
      ICanRemove<SomeClass>
{
    SomeClass Add(SomeClass entity);
    bool Remove(SomeClass entity);
}

让我们退后一步,看看为什么我认为这是比在一个通用接口中实现所有 CRUD 方法更好的实践。

有些对象的要求不同。客户对象可能不会删除,无法更新购买订单,并且只能创建购物车对象。当人们使用通用iRepository接口时,这显然会导致实现问题。

那些实施反模式的人通常会实现其完整界面,然后将为他们不支持的方法提供例外。除了不同意众多的OO原则外,这打破了他们能够有效地使用其iRepository抽象的希望,除非他们还开始在上面为是否支持对象并进一步实施它们。

这个问题的常见解决方法是转到更颗粒的界面,例如icandelete,icanupdate,icancreate等。这在解决了许多问题上的许多问题方面也大大减少了被视为大部分时间的代码重用量,人们将无法再使用储存库混凝土实例。

我们都不喜欢一遍又一遍地编写相同的代码。但是,建筑接缝的存储库合同是扩大合同以使其更通用的错误的地方。

这些摘录是无耻地摘自 这个帖子 您还可以在评论中阅读更多讨论。

关于1:据我所知,从存储库返回的问题不是 IQuerable 本身。存储库的要点是它应该看起来像一个包含所有数据的对象。所以你可以向存储库询问数据。如果您有多个对象需要相同的数据,则存储库的工作就是缓存数据,因此存储库的两个客户端将获得相同的实例 - 因此,如果一个客户端更改属性,另一个客户端将看到该数据,因为它们指向同一个实例。

如果存储库实际上是 Linq 提供程序本身,那么这就很合适。但大多数人只是让 Linq-to-sql 提供程序的 IQuerable 直接通过,这实际上绕过了存储库的责任。所以存储库根本不是存储库,至少根据我对模式的理解和使用。

关于2:当然,仅从数据库返回单个值比返回整个记录更具性能效率。但是使用存储库模式,您根本不会返回记录,而是返回业务对象。因此应用程序逻辑不应该关心字段,而应该关心域对象。

但是,与完整的域对象相比,返回单个值有多有效呢?如果您的数据库模式定义得相当好,您可能无法衡量差异。

拥有干净、易于理解的代码比预先进行微观性能优化要重要得多。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top