11

我在 RavenDB 项目中实现存储库和服务模式时遇到了一些困难。主要关心的是我的存储库界面应该是什么样子,因为在 RavenDB 中,我为查询使用了几个索引。

假设我需要获取 parentid 等于 1 的所有项目。一种方法是使用 IQueryable List() 并获取所有文档,然后添加 where 子句来选择 parentid 等于 1 的项目。这似乎是个坏主意因为我不能在 RavenDB 中使用任何索引功能。所以另一种方法是在存储库中有这样的东西,IEnumerable Find(string index, Func predicate) 但这似乎也是一个坏主意,因为它不够通用,并且如果我要从 RavenDB 更改,我需要实现此方法到一个普通的sql server。

那么如何实现通用存储库但仍能获得 RavenDB 中索引的好处呢?

4

4 回答 4

10

这篇文章很好地总结了这一切:

http://novuscraft.com/blog/ravendb-and-the-repository-pattern

于 2012-02-23T23:06:26.830 回答
10

首先,问你为什么要使用存储库模式?

如果您因为正在进行域驱动设计而想要使用该模式,那么正如另一个答案指出的那样,您需要重新考虑查询的意图,并根据您的域来讨论它 - 并且你可以开始围绕这个建模。

在这种情况下,规范可能是您的朋友,您应该研究它们。


但是,在继续我的回答之前,让我们先看一下您问题的一部分:

似乎是个坏主意,因为它不够通用,并且如果我要从 RavenDB 更改为通用 sql 服务器,则需要我实现此方法。

你的做法是错误的——试图让你的系统在这个级别上完全与持久性无关是自找麻烦——如果你试图对查询本身隐藏数据存储的独特功能,那么为什么还要使用 RavenDB?

我倾向于在简单的面向文档中使用的一种方法(IE,我确实在谈论数据,这就是您似乎正在做的事情),是将我的查询与我的命令分开。

问问自己,为什么要按父 ID 查询文档?是在页面上显示列表吗?那么,您为什么要尝试根据文档对此进行建模呢?为什么不根据视图模型对此进行建模并使用从 RavenDB 检索这些数据的最有效方法呢?(对索引(动态或其他方式)的查询),将其粘贴在接受“一些输入”并生成“输出”的工厂中,如果您决定更改持久性存储,则可以更改这些工厂。(我在我的 ASP.NET MVC 应用程序中更进一步,并且有单动作控制器,我不称它们为控制器,在大多数情况下从这些控制器中进行查询)。

如果您想按父 id 实际提取文档以更新它们或在它们之间运行一些业务逻辑,那么您可能错误地对它们建模 - 写入操作通常只涉及对单个文档的更改,或者换句话说您应该围绕事务边界对文档进行建模。

TL;博士

想想你真正想要实现的是什么——为什么要使用“存储库模式”或“服务模式”——这些词的存在是为了描述一个场景,如果你围绕你的应用程序建模你可能最终会遇到的情况。需求,作为表达某个对象角色的常用方式——而不是你需要将你的每一个功能都硬塞进去的东西。

于 2011-03-23T09:54:14.370 回答
8

假设我需要获取 parentid 等于 1 的所有项目。

首先,停止以这种方式考虑您的数据访问需求。

不需要获取 parentid 等于 1 的所有项目”。这将有助于尝试并停止以这种面向数据的方式进行思考。

您需要的是获取具有特定父级的所有项目。这是一个存在于您的问题空间(您的应用程序域)中的概念。

您在数据库中使用外键和名为 parentid 的字段对其建模这一事实是一个实现细节。封装它,不要在整个应用程序中泄漏它。

一种方法是使用 IQueryable List() 并获取所有文档,然后添加 where 子句来选择 parentid 等于 1 的项目。这似乎是个坏主意,因为我不能在 RavenDB 中使用任何索引功能。所以另一种方法是在存储库中有这样的东西, IEnumerable Find(string index, Func predicate) 但这似乎也是一个坏主意,因为

这两个都是坏主意。您的建议是要求调用您的存储库或查询的代码了解您的架构。

为什么你的存储库的消费者应该关心或知道有一个 parentid 字段?如果这种情况发生了变化,如果您的问题空间中某些特定概念的定义发生了变化,那么您的代码中有多少地方必须更改?

每个获取具有特定父项的项目的地方。

这很糟糕,它是封装的对立面。

我的观点是,您将希望将查询建模为显式概念,而不是 lambda 或字符串到处传递和使用。

您可以使用规范模式、存储库上的命名查询方法、查询对象模式等显式建模查询。

它不够通用,如果我要从 RavenDB 更改为通用 sql 服务器,则需要我实现此方法。

嗯,Func太笼统了。同样,考虑一下您的消费代码需要知道什么才能使用这种查询方法,您将代码的上层直接绑定到您的 DB 模式执行此操作。

此外,如果您从一个存储引擎更改为另一个,则无法避免重新实现查询,因为性能足以使用特定于存储引擎的辅助工具(例如 Raven 中的索引)。

于 2011-03-23T01:08:45.003 回答
5

我实际上不鼓励您使用存储库模式。在大多数情况下,它过度架构并且实际上使代码更加复杂。

Ayende 最近为此发表了许多帖子:

我建议只针对 Raven 的原生 API 编写代码。

如果您觉得我的回答过于笼统,请列出您希望从使用另一层抽象中获得的一些好处,我们可以继续讨论。

于 2011-03-23T00:45:31.757 回答