如何正确使用存储库模式?


86

我想知道应该如何对存储库进行分组?就像在asp.net mvc上看到的示例一样,在我的书中,它们基本上每个数据库表都使用一个存储库。但这似乎是很多存储库,导致您以后不得不调用许多存储库以进行模拟和填充。

所以我想我应该将它们分组。但是我不确定如何将它们分组。

现在,我做了一个注册资料库来处理我所有的注册资料。但是,有4个表需要更新,而在我拥有3个存储库之前,需要进行更新。

例如,表之一是许可证表。当他们注册时,我查看他们的密钥并检查它是否存在于数据库中。现在,如果我需要检查该许可证密钥或该表中其他位置(而不是注册)的其他内容,该怎么办?

可以登录一个位置(检查密钥是否未过期)。

那么在这种情况下我该怎么办?再次重写代码(打破DRY)?尝试将这两个存储库放在一起,并希望在其他时间点都不需要使用任何方法(例如,也许我可能有一个检查是否使用了userName的方法-也许我会在其他地方使用该方法)。

另外,如果我将它们合并在一起,那么我将需要2个服务层到同一个存储库,因为我认为拥有站点2个不同部分的所有逻辑将很长,而且我必须具有ValidateLogin(),ValdiateRegistrationForm()之类的名称。 ,ValdiateLoginRetrievePassword()等。

还是仍然调用存储库,只是在周围有一个奇怪的名字?

似乎很难建立一个具有足够通用名称的存储库,以便您可以将其用于应用程序的许多位置并且仍然有意义,我认为在存储库中调用另一个存储库不是一个好习惯吗?


11
+1。好问题。
griegs

感谢它困扰了我一段时间。由于我发现我在书中看到的内容太简单了,因此无法向您显示在这些情况下该怎么做。
chobo2

如果我有一个不止一个存储库中使用的linq2sql类,并且我需要更改打破DRY的表结构,我基本上会和你做同样的事情。不太理想。现在,我计划得更好一些,因此我不需要多次使用linq2sql类,我认为这是关注点分离的好方法,但是我预见到有一天这对我来说将是一个真正的问题。
griegs

我在这里问了类似的问题(但不完全相同):stackoverflow.com/questions/910156/…–
Grokys

是的,但似乎很难针对所有情况进行规划。就像我说的,我可以将登录名和注册合并到Authentication中,并具有2个单独的层。那可能会解决我的问题。但是,如果说我网站的个人资料页面时,无论出于何种原因我想向他们显示他们的密钥,那该怎么办(也许我会让其更改)。现在我该怎么做才能打破DRY并写同样的东西?或者尝试建立一个可以以某种方式使所有三个表适合的存储库。
chobo2

Answers:


41

在处理存储库模式时,我做错了一件事-就像您一样,我认为该表与存储库1:1有关。当我们应用域驱动设计中的某些规则时-分组存储库问题通常会消失。

存储库应该是每个聚合根目录,而不是表。这意味着-如果实体不应该一个人住(即-如果您有一个Registrant特别参与Registration的实体)-它只是一个实体,它不需要存储库,应该通过聚合根的存储库对其进行更新/创建/检索属于。

当然-在很多情况下,这种减少存储库数量的技术(实际上-这是一种用于构建域模型的技术)无法应用,因为每个实体都应该是一个聚合根(这在很大程度上取决于您的域,我只能提供盲目的猜测)。在您的示例中-License似乎是一个聚合根,因为您需要能够在没有Registration实体上下文的情况下检查它们。

但这并不限制我们级联存储库(如果需要,Registration允许存储库引用License存储库)。这并不限制我们License直接从Registration对象引用存储库(最好是通过IoC)。

只是不要试图通过技术带来的复杂性或误解来推动您的设计。ServiceX仅因为您不想构造2个存储库而将存储库分组并不是一个好主意。

最好给它起一个合适的名字-RegistrationService

但是一般应避免使用服务-它们通常是导致贫血领域模型的原因

编辑:
开始使用IoC。它确实减轻了注入依赖项的痛苦。
而不是写:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

您将能够写:

var registrationService = IoC.Resolve<IRegistrationService>();

Ps最好使用所谓的公共服务定位器,但这只是一个例子。


17
哈哈哈...我正在提供有关使用服务定位器的建议。看到我过去有多愚蠢,总是很高兴。
阿尼斯·拉普萨

1
@Arnis听起来您的意见似乎已经改变了-我很感兴趣您现在如何以不同的方式回答这个问题?
ngm

10
自从我回答这个问题以来,@ ngm发生了很大变化。我仍然同意聚合根应该划定事务边界(作为整体保存),但是我对使用存储库模式抽象持久性的乐观程度要低得多。最近-我只是直接使用ORM,因为渴望/延迟加载管理之类的事情太尴尬了。专注于开发丰富的域模型,而不是专注于抽象持久性,这将更为有益。
2011年

2
@Developer不,不完全是。他们仍然应该坚持不懈。您可以从外部检索聚合根并在其上调用完成该工作的方法。我的域模型的引用为零,而标准.net框架的引用为零。为了实现这一目标,您必须拥有足够聪明的丰富域模型和工具(NHibernate可以解决问题)。
2011年

1
反之。获取多个集合通常意味着您可以使用PK或索引。比利用EF生成的关系要快得多。根聚合越小,就越容易获得性能。
jgauffin

5

我已开始解决此问题的一件事是实际开发包装N个存储库的服务。希望您的DI或IoC框架可以帮助您简化这一过程。

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

那有意义吗?另外,我了解到,在此庄园中谈论服务实际上可能符合或不符合DDD原则,我这样做是因为它似乎有效。


1
不,那没有多大意义。我目前不使用DI或IoC框架,因为我已经掌握了足够的信息。
chobo2

1
如果可以仅使用new()实例化存储库,则可以尝试以下方法... public ServiceImpl():this(new Repo1,new Repo2 ...){}作为服务中的其他构造函数。
neouser99 2009年

依赖注入?我已经这样做了,但仍然不确定您的代码是什么,以及它可以解决什么。
chobo2

我特别打算合并存储库。哪个代码没有意义?如果您使用的是DI,那么答案中的代码将在某种程度上起作用,即您的DI框架将注入那些IRepo服务,在注释代码中,基本上只是做DI的一小步(基本上,无参数构造函数会“注入”这些依赖项放到您的ServiceImpl中)。
neouser99

我已经使用了DI,因此可以更好地进行单元测试。我的问题是,如果您将服务层和存储库关联到详细的名称中。然后,如果您需要在其他任何地方使用它们,它将看起来很奇怪,就像在其他类中调用RegRepo的RegistrationService层(例如ProfileClass)那样。因此,我没有从您的榜样中看到您所做的一切。就像如果您在同一个服务层中开始有太多的Repos,您将拥有太多不同的业务逻辑和验证逻辑。由于通常在服务层中放入验证逻辑。我需要更多...
chobo2

2

我正在做的是定义一个抽象基类,如下所示:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

然后,您可以从抽象基类派生您的存储库,并在通用参数不同的情况下扩展您的单个存储库。

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

等等....

我需要单独的存储库,因为我们确实对某些存储库有限制,这给了我们最大的灵活性。希望这可以帮助。


因此,您试图建立一个通用存储库来处理所有这一切吗?
chobo2

所以实际上是说Update方法。就像您有此V更新一样,它将T实体传递给Update,但实际上没有代码对其进行更新吗?
chobo2

是的。update方法中将包含代码,因为您将编写一个从通用存储库派生的类。实现代码可以是Linq2SQL或ADO.NET,也可以是您选择用作数据访问实现技术的任何一种代码
Michael Mann

2

我有这个作为我的存储库类,是的,我在表/区域存储库中进行了扩展,但是有时我还是不得不中断DRY。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

编辑

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

我也有一个使用Linq2SQL.dbml类创建的datacontext。

因此,现在我有一个标准存储库,可以实现诸如All和Find之类的标准调用,并且在AdminRepository中有特定的调用。

尽管我不认为,但没有回答DRY的问题。


什么是“存储库”?并喜欢CreateInstance吗?不知道您所做的一切。
chobo2

它是通用的。基本上,您需要为您的(区域)有一个特定的存储库。请查看上面对我的AdminRepository所做的编辑。
griegs

1

这是使用FluentNHibernate的通用存储库实现的示例。它能够保留您为其编写映射器的任何类。它甚至能够根据映射器类生成数据库。


1

存储库模式是错误的设计模式。我使用许多旧的.Net项目,这种模式通常会导致“分布式事务”,“部分回滚”和“连接池已耗尽”错误,这些错误可以避免。问题在于,该模式尝试在内部处理连接和事务,但是这些连接和事务应在控制器层处理。EntityFramework也已经抽象了很多逻辑。我建议使用服务模式代替重用共享代码。


0

我建议您看一下Sharp Architecture。他们建议每个实体使用一个存储库。我目前在我的项目中使用它,对结果感到满意。


我想在这里给+1,但他表示DI或IoC容器不是一个很好的选择(当然,这并不是Sharp Arch的唯一好处)。我猜他正在研究许多现有代码。
neouser99 2009年

什么是实体?那是整个数据库吗?还是那个数据库表?目前,DI或IoC容器不是一个选择,因为我不想在同时学习的其他10件事上学习这一知识。它们是我将研究我的网站的下一个修订版或下一个项目的内容。即使我不确定是否会快速浏览一下该站点,并且似乎希望您使用nhirbrate,并且目前我正在使用linq to sql。
chobo2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.