在工作面试中,我被要求解释为什么存储库模式不是与诸如实体框架之类的ORM一起使用的好模式。为什么会这样呢?
在工作面试中,我被要求解释为什么存储库模式不是与诸如实体框架之类的ORM一起使用的好模式。为什么会这样呢?
Answers:
我看不出任何原因导致存储库模式不适用于Entity Framework。存储库模式是放在数据访问层上的抽象层。您的数据访问层可以是任何东西,从纯ADO.NET存储过程到Entity Framework或XML文件。
在大型系统中,数据来自不同的源(数据库/ XML / Web服务),最好有一个抽象层。在这种情况下,存储库模式效果很好。我认为实体框架不足以掩盖幕后发生的事情。
我已将Repository模式与Entity Framework一起用作我的数据访问层方法,但尚未遇到问题。
DbContext
使用Repository 提取的另一个优势是单元可测试性。您可以IRepository
拥有两个实现的接口,第一个实现(DbContext
用于与数据库进行通信的真实存储库),第二个FakeRepository
实现可以返回内存中的对象/模拟的数据。这使您可以进行IRepository
单元测试,从而可以使用的其他代码部分IRepository
。
public interface IRepository
{
IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
private YourDbContext db;
private EFRepository()
{
db = new YourDbContext();
}
public IEnumerable<CustomerDto> GetCustomers()
{
return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
}
}
public MockRepository : IRepository
{
public IEnumerable<CustomerDto> GetCustomers()
{
// to do : return a mock list of Customers
// Or you may even use a mocking framework like Moq
}
}
现在使用DI,您将获得实现
public class SomeService
{
IRepository repo;
public SomeService(IRepository repo)
{
this.repo = repo;
}
public void SomeMethod()
{
//use this.repo as needed
}
}
不将存储库模式与Entity Framework结合使用的唯一最佳理由是什么?实体框架已经实现了存储库模式。DbContext
是您的UoW(工作单元),每个DbSet
都是存储库。在此之上实现另一层不仅是多余的,而且会使维护更加困难。
人们遵循模式而没有意识到模式的目的。对于存储库模式,其目的是抽象掉低级数据库查询逻辑。在以前在代码中实际编写SQL语句的年代,存储库模式是一种将SQL从分散在整个代码库中的单个方法中移出并将其本地化的一种方法。拥有诸如Entity Framework,NHibernate等之类的ORM可以替代此代码抽象,因此不需要模式。
但是,在您的ORM上创建抽象并不是一个坏主意,它没有UoW / repostitory那样复杂。我将采用一种服务模式,在此模式下,您可以构造一个应用程序可以使用的API,而无需知道或关心数据是来自实体框架,NHibernate还是Web API。这要简单得多,因为您只需在服务类中添加方法即可返回应用程序所需的数据。例如,如果您正在编写“待办事项”应用程序,则可能需要致电服务,以退回本周到期但尚未完成的项目。您的应用程序只知道如果需要此信息,它将调用该方法。在该方法内部以及一般在服务中,您将与Entity Framework或正在使用的其他任何对象进行交互。然后,如果您以后决定切换ORM或从Web API中提取信息,
听起来这可能是使用存储库模式的一个潜在论点,但是这里的主要区别在于服务是一个较薄的层,旨在返回完全烘焙的数据,而不是您继续查询的内容,例如资料库。
DbContext
在EF6 +中进行模拟(请参阅:msdn.microsoft.com/en-us/data/dn314429.aspx)。即使在较小的版本中,由于实现了iterface,您也可以将DbContext
类DbSet
s 用作伪类。DbSet
IDbSet
这是来自Ayende Rahien的一张照片:在厄运的深渊中进行架构设计:存储库抽象层的弊端
我不确定我是否同意他的结论。这是一个陷阱22-一方面,如果我使用查询特定的数据检索方法将我的EF Context包装在特定于类型的存储库中,则实际上我可以对我的代码进行某种形式的单元测试,这对于Entity几乎是不可能的仅框架。另一方面,我失去了进行丰富的关系查询和语义维护的能力(但是,即使我可以完全访问这些功能,我也总是觉得自己像是在EF或我可能选择的任何其他ORM周围的蛋壳上行走,因为我从不知道其IQueryable实现可能支持或可能不支持哪些方法,因此它将是将我添加到导航属性集合中的内容解释为创建还是仅将其解释为是懒散的还是渴望加载的,或者根本不加载默认,等等。所以也许这是更好的。零阻抗对象关系“映射”是一种神话生物-也许这就是为什么最新版本的Entity Framework代号为“ Magic Unicorn”的原因。
但是,通过特定于查询的数据检索方法来检索您的实体意味着您的单元测试现在基本上是白盒测试,因此您别无选择,因为您必须事先确切知道被测单元将使用哪种存储库方法。呼叫以模拟它。而且,除非您还编写集成测试,否则您实际上还没有真正测试查询本身。
这些都是复杂的问题,需要复杂的解决方案。您不能仅通过假装所有实体都是单独的类型并且彼此之间没有任何关系并将它们原子化到各自的存储库中来进行修复。那么你可以,但它吮吸。
更新:使用实体框架的Effort提供程序取得了一些成功。Effort是一个内存中提供程序(开放源代码),可让您完全按照对实际数据库使用EF的方式在测试中使用EF。我正在考虑使用该提供程序来切换该项目中的所有测试,因为它似乎使事情变得如此简单。这是迄今为止我发现的唯一解决方案,它可以解决我先前提出的所有问题。唯一的问题是,在开始测试时会稍有延迟,因为它正在创建内存数据库(它使用另一个名为NMemory的软件包来执行此操作),但是我认为这不是真正的问题。有一篇代码项目文章讨论了如何使用Effort(相对于SQL CE)进行测试。
DbContext
。无论如何,您总是可以嘲笑DbSet
,无论如何,这就是Entity Framework的实质。DbContext
仅是一个类,用于DbSet
在一个位置(工作单元)中存储属性(存储库),尤其是在单元测试环境中,该环境始终不需要或不需要所有数据库初始化和连接的东西。
您可能会这样做的原因是因为它有点多余。实体框架为您提供了丰富的编码和功能优势,这就是为什么要使用它,如果再采用它并将其包装在存储库模式中,则会丢掉这些优势,那么您可能还会使用任何其他数据访问层。
从理论上讲,我认为封装数据库连接逻辑以使其更易于重用是有意义的,但是正如下面的链接所述,我们的现代框架现在基本上已经在处理此问题。
ISessionFactory
并且ISession
很容易嘲笑),但是DbContext
不幸的是,这并不容易...
使用存储库模式的一个很好的理由是允许将业务逻辑和/或UI与System.Data.Entity分离。这有很多优点,包括允许他使用Fakes或Mocks进行单元测试的实际好处。
在小型项目上尝试了存储库模式后,我强烈建议不要使用它;不是因为它使您的系统变得复杂,不是因为模拟数据是噩梦,而是因为您的测试变得无用!
通过模拟数据,您可以添加不带标题的详细信息,添加违反数据库约束的记录以及删除数据库拒绝删除的实体。在现实世界中,单个更新可能会影响多个表,日志,历史记录,摘要等,以及诸如上次修改日期字段,自动生成的键,计算字段之类的列。
简而言之,在真实数据库上运行测试将为您带来真实的结果,并且不仅可以测试服务和接口,还可以测试数据库行为。您可以检查存储过程是否对数据进行了正确的处理,是否返回了预期的结果,或者您发送删除的记录是否确实被删除了!这样的测试还会暴露出诸如忘记从存储过程中引发错误等问题,以及成千上万的此类情况。
我认为,实体框架实现的存储库模式要比我到目前为止阅读的任何文章都更好,并且远远超出了他们试图完成的工作。
在我们使用XBase,AdoX和Ado.Net并使用实体的日子里,存储库是最佳实践!(存储库超过存储库)
最后,我认为太多的人在学习和实现存储库模式上投入了大量时间,他们拒绝放手。主要是为了向自己证明他们没有浪费时间。