12

目前我们已经在工作中实现了一个存储库模式。我们所有的存储库都位于它们自己的接口后面,并通过 Ninject 映射。我们的项目非常大,我正在尝试解决这种模式的一些怪癖。

首先,在一些控制器中,我们需要在同一个控制器中拥有超过 10 到 15 个存储库。当请求这么多存储库时,构造函数变得相当丑陋。在您调用多个存储库的方法后,第二个怪癖就会显现出来。在处理完多个存储库之后,我们需要调用 SaveChanges 方法,但是我们应该在哪个存储库上调用它呢?每个存储库都有一个。所有存储库都注入了相同的实体框架数据上下文实例,因此选择任何随机存储库来调用 save 都将起作用。只是看起来很乱。

我查看了“工作单元”模式并提出了一个我认为可以解决这两个问题的解决方案,但我对这个解决方案并不是 100% 有信心。我创建了一个名为DataBucket.

// Slimmed down for readability
public class DataBucket
{
    private DataContext _dataContext;

    public IReportsRepository ReportRepository { get; set; }
    public IEmployeeRepository EmployeeRepository { get; set; }
    public IDashboardRepository DashboardRepository { get; set; }

    public DataBucket(DataContext dataContext,
        IReportsRepository reportsRepository,
        IEmployeeRepository employeeRepository,
        IDashboardRepository dashboardRepository)
    {
        _dataContext = dataContext;
        this.ReportRepository = reportsRepository;
        this.EmployeeRepository = employeeRepository;
        this.DashboardRepository = dashboardRepository;
    }

    public void SaveChanges()
    {
        _dataContext.SaveChanges();
    }
}

这似乎解决了这两个问题。现在SaveChanges数据桶本身只有一种方法,您只注入一个对象,即数据桶。然后,您可以将所有存储库作为属性访问。数据桶看起来会有点凌乱,因为它会在其构造函数中接受我们的所有(很容易 50 或更多)存储库。

添加新存储库的过程现在包括:创建接口、创建存储库、在 Ninject 中映射接口和存储库,以及向数据存储桶添加属性并填充它。

我确实想到了一种替代方法,可以消除上面的步骤。

public class DataBucket
{
    private DataContext _dataContext;

    public IReportsRepository ReportRepository { get; set; }
    public IEmployeeRepository EmployeeRepository { get; set; }
    public IDashboardRepository DashboardRepository { get; set; }

    public DataBucket(DataContext dataContext)
    {
        _dataContext = dataContext;
        this.ReportRepository = new ReportsRepository(dataContext);
        this.EmployeeRepository = new EmployeeRepository(dataContext);
        this.DashboardRepository = new DashboardRepository(dataContext);
    }

    public void SaveChanges()
    {
        _dataContext.SaveChanges();
    }
}

这几乎消除了 Ninject 中的所有存储库映射,因为它们都在数据桶中实例化。所以现在添加新存储库的步骤包括:创建接口、创建存储库、向数据桶添加属性和实例化。

你能看出这个模型有什么缺陷吗?从表面上看,以这种方式使用我们的存储库似乎更方便。这是以前解决过的问题吗?如果是这样,解决这个问题的最常见和/或最有效的方法是什么?

4

4 回答 4

6

首先,在一些控制器中,我们需要在同一个控制器中拥有超过 10 到 15 个存储库。

向抽象工厂模式问好。无需在 Ninject 中注册所有存储库并将它们注入控制器,而是仅注册工厂的单个实现,它将能够提供您需要的任何存储库 - 您甚至可以仅在控制器确实需要它们时才懒惰地创建它们。比将工厂注入控制器。

是的,它也有一些缺点 - 您正在授予控制器获取任何存储库的权限。对你来说有问题吗?如果需要,您始终可以为某些子系统创建多个工厂,或者在单个实现上简单地公开多个工厂接口。它仍然没有涵盖所有情况,但它比将 15 个参数传递给构造函数要好。顺便提一句。你确定那些控制器不应该分开吗?

注意:这不是服务提供者反模式。

在处理完多个存储库之后,我们需要调用 SaveChanges 方法,但是我们应该在哪个存储库上调用它呢?

向工作单元模式问好。工作单元是应用程序中的逻辑事务。它将逻辑事务中的所有更改保存在一起。存储库不应该负责持久化更改 - 工作单元应该是。有人提到这DbContext是存储库模式的实现。它不是。它是工作单元模式DbSet的实现,是存储库模式的实现。

您需要的是持有上下文实例的中心类。上下文也将传递给存储库,因为它们需要它来检索数据,但只有中心类(工作单元)将提供保存更改。例如,如果您需要更改隔离级别,它还可以处理数据库事务。

应该在哪里处理工作单元?这取决于您的逻辑操作在哪里编排。如果操作直接在控制器的操作中编排,则您还需要在操作中包含工作单元,并在SaveChanges所有修改完成后调用。

如果您不太关心关注点分离,您甚至可以将工作单元和工厂组合成一个类。这将我们带到您的DataBucket.

于 2012-08-23T19:43:46.653 回答
2

我认为在这种情况下使用工作单元模式是绝对正确的。这不仅可以防止您SaveChanges在每个存储库上都需要一个方法,它还为您提供了一种从代码内部而不是在数据库本身中处理事务的好方法。我在我的 UOW 中包含了一个Rollback方法,这样如果出现异常,我可以撤消该操作已经对我的DataContext.

为了防止奇怪的依赖问题,您可以做的一件事是将相关存储库分组到它们自己的工作单元中,而不是拥有一个包含您拥有的每个存储库的大 DataBucket(如果这是您的意图)。每个 UOW 只需要在与其包含的存储库相同的级别上可访问,并且其他存储库可能不应该依赖于其他 UOW 本身(您的存储库不应该需要使用其他存储库)。

如果想成为该模式的更纯粹主义者,您还可以构建您的 UOW 来代表它,即单个工作单元。您将它们定义为代表域中的特定操作,并为其提供完成该操作所需的存储库。如果在您的域中被多个操作使用是有意义的,则单个存储库可以存在于多个 UOW 上。

例如, aPlaceCustomerOrderUnitOfWork可能需要 a CustomerRepositoryOrderRepositoryBillingRepository和 aShippingRepository

AnCreateCustomerUnitOfWork可能只需要一个CustomerRepository. 无论哪种方式,您都可以轻松地将这种依赖传递给它的使用者,为您的 UOW 提供更细粒度的接口可以帮助您针对测试并减少创建模拟的工作量。

于 2012-08-23T18:49:02.230 回答
1

每个存储库都有一个的概念SaveChanges是有缺陷的,因为调用它可以保存一切。无法修改 a 的一部分DataContext,您始终保存所有内容。所以一个中央DataContext持有者类是一个好主意。

或者,您可以拥有一个具有通用方法的存储库,可以对任何实体类型(GetTable<T>、、Query<T>...)进行操作。这将摆脱所有这些类并将它们合并为一个(基本上,只剩下DataBucket)。

甚至可能根本不需要存储库:您可以注入它DataContext自己!它DataContext本身就是一个存储库和一个成熟的数据访问层。但它并不适合嘲笑。

如果你能做到这一点,取决于你需要“存储库”提供什么。


拥有该类的唯一问题DataBucket是该类需要了解所有实体和所有存储库。所以它在软件堆栈中的位置非常高(在顶部)。同时它基本上被所有东西使用,所以它也位于底部。等待!这是对整个代码库的依赖循环

这意味着使用它的所有东西和它使用的所有东西都必须位于同一个程序集中。

于 2012-08-23T17:37:29.697 回答
0

我过去所做的是创建子注入容器(我使用 Unity)并使用ContainerControlledLifetime. 因此,当存储库被实例化时,它们总是相同的数据上下文注入其中。然后,我继续使用该数据上下文,当我的“工作单元”完成时,我调用将DataContext.SaveChanges()所有更改刷新到数据库中。

这具有其他一些优点,例如(使用 EF)一些本地缓存,例如,如果多个存储库需要获取相同的实体,则实际上只有第一个存储库会导致数据库往返。

这也是“批量”更改并确保它们作为单个原子事务执行的好方法。

于 2012-08-23T17:41:41.620 回答