目前,我们已在工作中实施了存储库模式。我们所有的存储库都位于自己的接口后面,并通过Ninject进行映射。我们的项目非常庞大,我试图解决这个模式有几个怪癖。
首先,有些控制器我们需要在同一个控制器中有超过10到15个存储库。在要求这么多的存储库时,构造函数变得相当丑陋。在多个存储库上调用方法后,第二个怪癖就会显现出来。在使用多个存储库之后,我们需要调用SaveChanges方法,但是我们应该调用哪个存储库?每个存储库都有一个。所有存储库都注入了相同的Entity Framework数据上下文实例,因此选择任何随机存储库来调用save on都可以。它看起来很乱。
我查看了“工作单元”模式并提出了一个我认为可以解决这两个问题的解决方案,但我对这个解决方案并不是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中的所有存储库映射,因为它们都在数据桶中实例化。所以现在添加新存储库的步骤包括:创建接口,创建存储库,向数据存储区添加属性并实例化。
你能看到这个型号的任何缺陷吗?从表面上看,以这种方式使用我们的存储库似乎更方便。这是一个以前解决过的问题吗?如果是这样,这个问题最常见和/或最有效的方法是什么?
答案 0 :(得分:5)
首先,有一些控制器,我们需要在同一个控制器中有超过10到15个存储库。
向抽象工厂模式问好。而不是在Ninject中注册所有存储库并将它们注入控制器只注册工厂的单个实现,它将能够提供您需要的任何存储库 - 您甚至可以只在控制器真正需要它时才懒惰地创建它们。而不是将工厂注入控制器。
是的,它也有一些缺点 - 你给予控制器权限来获取任何存储库。这对你有用吗?如果需要,您可以随时为某些子系统创建多个工厂,或者只在单个实现上公开多个工厂接口。它仍然没有涵盖所有情况,但它比将15个参数传递给构造函数更好。顺便说一句。你确定那些控制器不应拆分吗?
注意:这不是服务提供商的反模式。
在完成多个存储库的工作后,我们需要调用SaveChanges方法,但是我们应该调用哪个存储库?
向工作单元模式问好。工作单元是您的应用程序中的逻辑事务。它将逻辑事务中的所有更改保持在一起。存储库不应对持续更改负责 - 工作单元应该是。有人提到DbContext
是Repository模式的实现。 It is not。它是工作单元模式的实现,DbSet
是存储库模式的实现。
您需要的是持有上下文实例的中心类。上下文也将传递给存储库,因为它们需要它来检索数据,但只有中心类(工作单元)才会提供保存更改。如果您需要更改隔离级别,它也可以处理数据库事务。
应该在哪个单位处理? That depends您的逻辑操作是在哪里编排的。如果操作是直接在控制器的操作中编排的,则您还需要在操作中包含工作单元,并在完成所有修改后调用SaveChanges
。
如果你不太关心问题的分离,你甚至可以combine unit of work and factory进入单课。这会将我们带到您的DataBucket
。
答案 1 :(得分:2)
我认为在这种情况下使用工作单元模式是绝对正确的。这不仅会阻止您在每个存储库上都需要SaveChanges
方法,它还为您提供了一种从代码中而不是数据库本身处理事务的好方法。我在我的UOW中包含了一个Rollback
方法,这样如果有异常,我可以撤消操作已经在DataContext
上进行的任何更改。
您可以做的一件事是防止奇怪的依赖性问题,将相关的存储库分组到他们自己的工作单元上,而不是拥有一个容纳每个存储库的大型DataBucket(如果这是您的意图)。每个UOW只需要在与其包含的存储库相同的级别访问,而其他存储库可能不应该依赖于其他UOW本身(您的存储库不应该使用其他存储库)。
如果想成为一个更大的模式纯粹主义者,你也可以构建你的UOW来代表那个单一的工作单元。您可以定义它们以表示域中的特定操作,并为其提供完成该操作所需的存储库。如果您的域中有多个操作使用,则单个存储库可以存在于多个UOW上。
例如,PlaceCustomerOrderUnitOfWork
可能需要CustomerRepository
,OrderRepository
,BillingRepository
和ShippingRepository
CreateCustomerUnitOfWork
可能只需要一个CustomerRepository
。无论哪种方式,您都可以轻松地将该依赖关系传递给其消费者,UOW的更细粒度的界面可以帮助您定位测试并减少创建模拟的工作量。
答案 2 :(得分:1)
每个具有SaveChanges
的存储库的概念都存在缺陷,因为调用它可以保存所有内容。无法修改DataContext
的一部分,您始终可以保存所有内容。所以中心DataContext
持有者类是一个好主意。
或者,您可以拥有一个包含泛型方法的存储库,这些方法可以对任何实体类型(GetTable<T>
,Query<T>
,...)进行操作。这将摆脱所有这些类并将它们合并为一个(基本上只剩下DataBucket
)。
甚至可能根本不需要存储库:您可以注入DataContext
本身! DataContext
本身 是一个存储库和一个完整的数据访问层。但它不适合嘲笑。
如果你能做到这一点取决于你所需要的“存储库”提供的内容。
拥有DataBucket
类的唯一问题是该类需要了解所有实体和所有存储库。因此它位于软件堆栈中非常高(位于顶部)。与此同时它基本上被所有东西使用,所以它也位于底部。等待!这是整个代码库的依赖循环。
这意味着使用它的所有内容以及它所使用的所有内容都必须位于同一个程序集中。
答案 3 :(得分:0)
我过去所做的是创建子注入容器(我使用Unity)并使用ContainerControlledLifetime
注册数据上下文。因此,当实例化存储库时,始终会将相同的数据上下文注入其中。然后,我继续讨论数据上下文以及我的工作单位&#34;完成后,我调用DataContext.SaveChanges()
将所有更改刷新到数据库。
这有一些其他优点,例如(使用EF)一些本地缓存,这样如果多个存储库需要获取相同的实体,则只有第一个存储库实际上导致数据库往返。
这也是一个很好的方式来批量生产&#34;更改并确保它们作为单个原子事务执行。