46

(注意:我的问题与三个月前提出这个问题的人非常相似,但从未得到回答。)

我最近开始使用 MVC3 + Entity Framework,我一直在阅读,最佳实践是使用存储库模式来集中访问 DAL。这还伴随着您希望将 DAL 与域(尤其是视图层)分开的解释。但在我看到的示例中,存储库是(或似乎是)简单地返回 DAL 实体,即在我的情况下,存储库将返回 EF 实体。

所以我的问题是,如果存储库只返回 DAL 实体,它有什么好处?这不会增加一层复杂性,不会消除在层之间传递 DAL 实体的问题吗?如果存储库模式创建了“进入 DAL 的单一入口点”,那与上下文对象有何不同?如果存储库提供了一种机制来检索和持久化 DAL 对象,那么它与上下文对象有何不同?

另外,我至少在一个地方读到了工作单元模式集中存储库访问以管理数据上下文对象,但我也不明白为什么这很重要。

我有 98.8% 的把握我在这里遗漏了一些东西,但从我的阅读中我没有看到它。当然,我可能只是没有阅读正确的资料...:\

4

7 回答 7

59

我认为术语“存储库”通常被认为是在Martin Fowler的“企业应用程序架构模式”一书中描述的“存储库模式”。

存储库在域和数据映射层之间进行调解,就像内存中的域对象集合一样。客户端对象以声明方式构造查询规范,并将它们提交给 Repository 以获得满意。对象可以添加到存储库中,也可以从存储库中删除,就像它们可以从简单的对象集合中一样,存储库封装的映射代码将在幕后执行适当的操作。

从表面上看,Entity Framework 完成了所有这些,并且可以用作存储库的简单形式。但是,存储库可能不仅仅是数据层抽象。

根据 Eric Evans 的《领域驱动设计》一书,存储库具有以下优点:

  • 他们为客户提供了一个简单的模型,用于获取持久性对象并管理它们的生命周期
  • 它们将应用程序和域设计与持久性技术、多种数据库策略甚至多个数据源解耦
  • 他们传达有关对象访问的设计决策
  • 它们允许轻松替换虚拟实现以进行单元测试(通常使用内存中的集合)。

第一点大致相当于上面那一段,不难看出Entity Framework本身很容易做到。

有人会争辩说 EF 也完成了第二点。但通常 EF 仅用于将每个数据库表转换为 EF 实体,并将其传递给 UI。它可能抽象了数据访问的机制,但几乎没有抽象出幕后的关系数据结构。

在主要面向数据的简单应用程序中,这似乎不是重点。但是随着应用程序的领域规则/业务逻辑变得更加复杂,您可能希望更加面向对象。数据的关系结构包含对业务领域不重要的特性,但这些特性是数据存储的副作用,这种情况并不少见。在这种情况下,仅仅抽象持久性机制是不够的,还要抽象数据结构本身的性质。单独的 EF 通常不会帮助您做到这一点,但存储库层会。

至于第三个优势,EF 不会(从 DDD 的角度)提供帮助。通常 DDD 不仅使用存储库来抽象数据持久性的机制,而且还提供有关如何访问某些数据的约束:

我们也不需要对更方便通过遍历查找的持久对象进行查询访问。例如,可以从 Person 对象请求一个人的地址。最重要的是,禁止访问 AGGREGATE 内部的任何对象,除非从根遍历。

换句话说,您不会仅仅因为您的数据库中有一个地址表而有一个“AddressRepository”。如果您的设计选择以这种方式管理 Address 对象的访问方式,那么 PersonRepository 就是您定义和实施设计选择的地方。

此外,DDD 存储库通常是封装与域数据集相关的某些业务概念的地方。OrderRepository 可能有一个名为OutstandingOrdersForAccount 的方法,它返回一个特定的Orders 子集。或者,客户存储库可能包含 PreferredCustomerByPostalCode 方法。

如果没有添加存储库抽象层,Entity Framework 的 DataContext 类不能很好地适应这种功能。它们适用于 DDD 所谓的规范,它可以是简单的布尔表达式,发送到一个简单的方法,该方法将根据表达式评估数据并返回匹配项。

至于第四个优势,虽然我确信有某些策略可能会让一个人替代数据上下文,但将其包装在存储库中会使它变得非常简单。

关于“工作单元”,这是 DDD 书中所说的:

将事务控制权留给客户端。虽然 REPOSITORY 会插入和删除数据库,但它通常不会提交任何内容。例如,在保存后提交是很诱人的,但客户端可能具有正确启动和提交工作单元的上下文。如果 REPOSITORY 不动手,事务管理会更简单。

于 2012-11-02T04:04:21.380 回答
25

实体框架DbContext基本上类似于存储库(以及工作单元)。您不必在简单的场景中将其抽象出来。

存储库的主要优点是您的域可以是无知的并且独立于持久性机制。在基于层的架构中,依赖关系从 UI 层向下通过域(或通常称为业务逻辑层)指向数据访问层。这意味着 UI 依赖于 BLL,而 BLL 本身又依赖于 DAL。

在更现代的架构中(通过域驱动设计和其他面向对象的方法传播),域应该没有向外指向的依赖项。这意味着 UI、持久性机制和其他一切都应该依赖于域,而不是相反。

然后,存储库将通过其在域内的接口表示,但在域外的持久性模块中具有其具体实现。这样域只依赖于抽象接口,而不是具体的实现。

这基本上是面向对象与架构级别的过程编程。

另请参阅Ports and Adapters aka Hexagonal Architecture

存储库的另一个优点是您可以为各种数据源创建类似的访问机制。不仅适用于数据库,还适用于基于云的商店、外部 API、第三方应用程序等。

于 2012-11-01T15:47:12.330 回答
6

没错,在这些简单的情况下,存储库只是 DAO 的另一个名称,它只带来一个价值:您可以将 EF 切换到另一种数据访问技术。今天你使用 MSSQL,明天你会想要一个云存储。或者使用 micro orm 而不是 EF 或从 MSSQL 切换到 MySql。

在所有这些情况下,最好使用存储库,因为应用程序的其余部分不会关心您现在使用的存储空间。

在有限的情况下,您从多个来源(db + 文件系统)获取信息,repo 将充当外观,但它仍然是 DAO 的另一个名称。

“真正的”存储库仅在您处理域/业务对象时才有效,对于不会更改存储的以数据为中心的应用程序,仅 ORM 就足够了。

于 2012-11-01T15:56:32.097 回答
2

鉴于您的情况,我会简单地选择一组接口来表示需要从数据层返回的数据结构(您的领域模型)。然后,您的实现可以是 EF、Raw ADO.Net 或任何其他类型的数据存储/提供程序的混合。这里的关键策略是将实现从直接消费者(您的域层)中抽象出来。当您想要对域对象进行单元测试时,这很有用,并且在不太常见的情况下 - 完全更改您的数据提供者/数据库平台。

如果还没有的话,您应该考虑使用IOC 容器,因为它们通过Dependency Injection使您的解决方案的松散耦合变得非常容易。 有很多可用的,我个人更喜欢Ninject

领域层应该封装你所有的业务逻辑——问题领域的规则和需求,并且可以被你的 MVC3 Web 应用程序直接使用。在某些情况下,引入位于域层之上的服务层是有意义的,但这并不总是必要的,并且对于简单的 Web 应用程序来说可能是多余的。

于 2012-11-01T16:16:07.393 回答
2

在您有多个数据源并希望使用一致的编码策略访问它们的情况下,它会很有用。

例如,您可能有多个 EF 数据模型,一些数据使用带有存储过程的传统 ADO.NET 访问,一些数据使用 3rd 方 API 访问,还有一些从位于 Windows NT4 服务器上的 Access 数据库访问扫帚柜里的灰尘。

您可能不希望您的业务或前端层关心数据的来源,因此您构建了一个通用存储库模式来访问“数据”,而不是访问“实体框架数据”。

在这种情况下,您的实际存储库实现将彼此不同,但调用它们的代码不会知道差异。

于 2012-11-01T15:52:43.117 回答
1

要考虑的另一件事是,即使您知道将使用单个数据存储,创建存储库抽象仍然可能有意义。原因是您的应用程序可能需要一个功能,而您的 ORM du jour 要么表现不佳(性能),要么根本没有,或者您只是不知道如何使 ORM 满足您的需求。

如果您将 ORM 包装在经过深思熟虑的存储库界面之后,您可以轻松地在您认为合适的不同技术之间切换。在我的存储库中看到一些方法使用 EF 进行工作而其他方法使用 PetaPoco 或(喘气)ADO.net 代码之类的方法并不少见。存储库抽象使您能够为手头的工作使用正确的工具,而不会将这些复杂性泄漏到客户端代码中。

于 2013-03-29T20:11:48.390 回答
0

我认为对许多文章所说的“存储库”存在很大的误解。这就是为什么人们怀疑这些抽象带来的真正价值。

在我看来,纯粹形式的存储库是 IEnumerable,而您和许多文章都在谈论“数据访问服务”。

我在这里写过博客。

于 2014-02-17T17:40:19.187 回答