在工作面试中,我被要求解释为什么存储库模式不是与诸如实体框架之类的ORM一起使用的好模式。为什么会这样?

评论

这是一个棘手的问题

我可能会对采访者回答说,Microsoft在演示实体框架时经常使用存储库模式:| 。

那么,面试官为什么不是一个好主意呢?

有趣的事实是,在Google中搜索“存储库模式”会得到与Entity Framework以及如何在EF中使用该模式有关的结果。

请查看ayende的博客ayende.com/blog。根据我的了解,他曾经使用存储库模式,但最终放弃了它,转而使用查询对象模式

#1 楼

我看不出任何原因导致存储库模式不适用于Entity Framework。存储库模式是放在数据访问层上的抽象层。您的数据访问层可以是任何东西,从纯ADO.NET存储过程到Entity Framework或XML文件。

在大型系统中,数据来自不同来源(数据库/ XML / Web服务),最好有一个抽象层。在这种情况下,存储库模式效果很好。我认为Entity Framework不足以掩盖幕后发生的事情。

我将Repository模式与Entity Framework一起用作我的数据访问层方法,但尚未遇到问题。

用存储库抽象DbContext的另一个优点是单元可测试性。您可以拥有一个IRepository接口,该接口具有2种实现,一种(真正的存储库)使用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
  }    
}


评论


我没有说过它不起作用,我也曾与EF一起使用存储库模式,但是今天我被问到为什么将这种模式与DataBase一起使用,使用数据库的应用程序为什么不好

– StringBuilder
2012年12月12日在21:08

好的,因为这是最受欢迎的答案,所以我将其选择为正确答案

– StringBuilder
2012年12月15日12:21

上一次最受欢迎的==什么时候是正确的?

– HDave
2013年8月30日19:16



DbContext已经是一个存储库,该存储库是低级抽象。如果要抽象不同的数据源,请创建对象来表示这些数据源。

–丹尼尔·利特尔(Daniel Little)
2013年12月3日下午4:34

ColacX。我们只是在控制器层尝试了这种方式-DBcontext-我们正在恢复到回购模式。使用Repo模式,单元测试源自不断失败的大规模DbContext模拟。 EF难以使用,并且对于EF细微差别的研究非常脆弱且耗时数小时。现在,我们有一些简单的回购模拟。代码更干净。分工更加清晰。我不再与众不同,EF已经是一种回购模式,并且已经可以进行单元测试。

–琉璃
16年7月11日在22:57

#2 楼

不将存储库模式与Entity Framework结合使用的唯一最佳理由是什么?实体框架已经实现了存储库模式。 DbContext是您的单位(工作量),每个DbSet是存储库。在此之上实现另一层不仅是多余的,而且会使维护变得更加困难。

人们遵循模式而没有意识到模式的目的。在存储库模式的情况下,目的是抽象掉低级数据库查询逻辑。在以前在代码中实际编写SQL语句的年代,存储库模式是一种将SQL从分散在整个代码库中的单个方法中移出并将其本地化的一种方法。拥有诸如Entity Framework,NHibernate等之类的ORM可以替代此代码抽象,因此无需使用该模式。

但是,在以下代码上创建抽象并不是一个坏主意在您的ORM的顶部,只是没有像UoW / repostitory那样复杂。我将采用一种服务模式,在该模式下,您可以构造一个应用程序可以使用的API,而无需知道或关心数据是来自实体框架,NHibernate还是Web API。这要简单得多,因为您只需在服务类中添加方法即可返回应用程序所需的数据。例如,如果您正在编写“待办事项”应用程序,则可能需要致电服务以返回本周到期但尚未完成的项目。您的应用程序所知道的就是,如果需要此信息,它将调用该方法。在该方法内部以及一般在服务中,您将与Entity Framework或您正在使用的其他任何对象进行交互。然后,如果您以后决定切换ORM或从Web API中获取信息,则只需更改服务,其余代码就可以顺利进行,而没有一个更明智。

听起来这可能是使用存储库模式的一个潜在论点,但是这里的主要区别在于服务是一个较薄的层,旨在返回完全烘焙的数据,而不是像您继续使用的那样继续查询的数据。存储库。

评论


这似乎是唯一正确的答案。

–迈克·张伯伦
2014年1月16日23:29

您可以在EF6 +中模拟DbContext(请参阅:msdn.microsoft.com/en-us/data/dn314429.aspx)。即使在较小的版本中,您也可以将伪造的类似DbContext的类与模拟的DbSets一起使用,因为DbSet实现了iterface IDbSet。

–克里斯·普拉特(Chris Pratt)
14 Mar 6 '14 at 0:23

@TheZenker,您可能未完全遵循存储库模式。最严格的区别是返回值。存储库返回可查询对象,而服务应返回可枚举对象。即使那不是真的那么黑白,因为那里有些重叠。更多有关如何使用它的信息。存储库应该只返回所有对象的集合,然后您可以进一步查询这些对象,而服务应该返回最终的数据集,并且不应该支持进一步的查询。

–克里斯·普拉特(Chris Pratt)
2014年3月27日14:31

冒着自负的风险:他们错了。现在,就官方教程而言,微软已经放弃使用自EF6以来所见过的存储库。关于这本书,我无法说出为什么作者选择使用存储库。作为正在开发大型应用程序的人,我可以说的是,将存储库模式与Entity Framework一起使用是维护的噩梦。一旦进入了比少数几个存储库更复杂的事物,最终将花费大量的时间来管理您的存储库/工作单元。

–克里斯·普拉特(Chris Pratt)
14-10-29在17:42

通常每个数据库或访问方法只有一项服务。我使用通用方法从同一组方法中查询多个实体类型。我使用Ninject将上下文注入到我的服务中,然后再将我的服务注入到我的控制器中,因此一切都变得整洁。

–克里斯·普拉特(Chris Pratt)
15年7月13日在14:42

#3 楼

这是来自Ayende Rahien的一张照片:在厄运的深渊中进行设计:存储库抽象层的弊端

我不确定我是否同意他的结论。这是一个陷阱22-一方面,如果我使用查询特定的数据检索方法将我的EF Context包装在特定类型的存储库中,则实际上我可以对我的代码进行某种形式的单元测试,这对于Entity几乎是不可能的仅框架。另一方面,我失去了进行丰富的关系查询和语义维护的能力(但是,即使我可以完全访问这些功能,我也总是觉得自己在EF或我可能选择的任何其他ORM周围的蛋壳上行走,因为我从不知道其IQueryable实现可能支持或可能不支持哪些方法,因此它将是将我添加到导航属性集合中的内容解释为创建还是仅将其解释为是懒散的还是渴望加载的,或者根本不加载零阻抗对象关系“映射”是一种神话生物,这也许就是为什么最新版本的Entity Framework代号为“ Magic Unicorn”的原因。

但是,通过特定于查询的数据检索方法来检索实体意味着您的单元测试现在基本上是白盒测试,您别无选择,因为您必须事先准确知道哪个存储库被测单元将要调用以模拟它的方法。而且,除非您还编写集成测试,否则您实际上还没有真正测试查询本身。

这些都是复杂的问题,需要复杂的解决方案。您不能仅通过假装所有实体都是单独的类型并且彼此之间没有任何关系并将它们原子化到各自的存储库中来进行修复。可以,但是很烂。

更新:使用实体框架的Effort提供程序取得了一些成功。 Effort是一个内存中提供程序(开放源代码),可让您完全按照对实际数据库使用EF的方式在测试中使用EF。我正在考虑使用该提供程序来切换该项目中的所有测试,因为这似乎使事情变得如此简单。这是迄今为止我发现的唯一解决方案,它可以解决我先前提出的所有问题。唯一的问题是,在开始测试时会稍有延迟,因为它正在创建内存数据库(它使用另一个称为NMemory的软件包来执行此操作),但是我认为这不是真正的问题。有一个Code Project文章讨论了如何使用Effort(相对于SQL CE)进行测试。

评论


任何没有提及单元测试的体系结构文章都会自动发送给我的垃圾箱。存储库模式的要点之一是获得一定的测试能力。

–睡眠者史密斯
13年2月19日在6:06

您仍然可以进行单元测试,而无需包装EF上下文(已经是存储库)。您应该对域/服务进行单元测试,而不是对数据库查询进行单元测试(它们是集成测试)。

–丹尼尔·利特尔(Daniel Little)
2013年12月3日下午4:36

EF的可测试性在版本6中得到了极大的改进。您现在可以完全模拟DbContext。无论如何,您始终可以模拟DbSet,无论如何,这就是Entity Framework的实质。 DbContext仅仅是一个类,用于在一个位置(工作单元)中存放DbSet属性(存储库),尤其是在单元测试上下文中,在该上下文中无论如何都不需要或不需要所有数据库初始化和连接的东西。

–克里斯·普拉特(Chris Pratt)
14 Mar 6 '14 at 0:29



松散相关实体导航很不好,而且是面向对象的操作,但是您将可以更好地控制所查询的内容。

– Alireza
15年5月30日在6:58

到测试点为止,EF Core在开箱即用的内存中和带有Sqlite提供程序的内存中已经实现了很长的路要进行单元测试。需要进行集成测试以在容器化数据库上运行测试时,请使用docker。

– Sudhanshu Mishra
19年6月27日在0:20

#4 楼

您可能会这样做的原因是因为它有点多余。实体框架为您提供了丰富的编码和功能优势,这就是为什么要使用它,如果再采用它并将其包装在存储库模式中,就将这些优势丢掉了,那么您可能还会使用任何其他数据访问层。 br />

评论


您能否谈谈“实体框架为您提供大量编码和功能上的优势”的一些优势?

– ManirajSS
15年10月29日在15:44

这就是他的意思。 var id = Entity.Where(i => i.Id == 1337).Single()封装并将其包装在存储库中,您基本上无法从外部进行这样的查询逻辑,这要么迫使您向A添加更多代码存储库和用于获取ID的接口。 B从存储库返回实体上下文,以便您可以编写查询逻辑(这只是废话)

– ColacX
16年1月21日在10:24



#5 楼

从理论上讲,我认为封装数据库连接逻辑以使其更易于重用是有意义的,但是正如下面的链接所述,我们的现代框架现在基本上已经在处理此问题。

重新考虑存储库模式

评论


我喜欢这篇文章,但企业应用程序的恕我直言,DAL和Bl之间的抽象层必须具有功能,因为您不知道明天将确切使用什么。但是感谢您分享链接

– StringBuilder
2012年12月12日在21:15

我个人认为,例如NHibernate(ISessionFactory和ISession可以轻松模拟),不幸的是,使用DbContext并不是那么容易。

–PatrykĆwiek
2012-12-28 23:02



#6 楼

使用存储库模式的一个很好的理由是允许将业务逻辑和/或UI与System.Data.Entity分离。这有很多优点,包括允许他使用Fakes或Mocks进行单元测试的真正好处。

评论


我同意这个答案。我的存储库基本上只是扩展方法,除了构建表达式树外什么都不做。通过非常简单的抽象,该抽象直接在dbcontext顶部直接提供了通用功能。抽象的唯一真正目的是使IoC更加容易。我认为人们尝试在存储库中执行不应执行的操作。他们根据每个实体进行回购,或将业务逻辑放在应该在服务层中的位置。您实际上只需要一个简单的通用存储库。它不是必须的,只需提供一致的接口即可。

–布兰登
16年3月24日在4:39

我只想添加一件事。是的,在大多数情况下,CQRS是一种非常优越的方法。对于我工作过的某些客户,当数据库专家不能与开发人员很好地合作时(这种情况发生的频率比人们想象的要高,尤其是在银行),EF over SQL是最好的选择。在这种特定情况下,当您完全无法控制数据库时,存储库模式就很有意义。因为它非常类似于数据结构,并且很容易将发生的事情转换为数据库,反之亦然。我认为这确实是一个政治和后勤决定。为了安抚DB之神。

–布兰登
16-3-24在4:56

实际上,我开始对此提出质疑。 EF是工作单元和存储库模式的组合。就像Chris Pratt上面在EF6中提到的那样,您可以轻松地模拟Context和DbSet对象。我仍然认为,应该将数据访问包装在类中,以使业务逻辑类与实际的数据访问机制隔离开来,但是要花大量精力并用另一个存储库包装EF,而工作单元抽象似乎是过大的选择。

–詹姆斯·库尔肖(James Culshaw)
16 Mar 26 '16 at 19:33

我不认为这是一个好答案,因为您的支持性陈述仅列出一个具有许多优点。您列出的一个优点不是一个好理由,因为您可以使用内存数据库来进行实体操作单元测试。

–乔尔·麦克贝斯(Joel McBeth)
17年3月14日在23:18

@jcmcbeth,如果您直接在上方查看我的评论,您会发现我对存储库模式和EF的最初看法已经改变。

–詹姆斯·库尔肖(James Culshaw)
17年3月15日在16:12

#7 楼

在小型项目上试用了存储库模式后,我强烈建议不要使用它;不是因为它使您的系统变得复杂,不是因为模拟数据是噩梦,而是因为您的测试变得无用!!

模拟数据使您可以添加没有标题的详细信息,添加违反数据库约束的记录并删除实体数据库将拒绝删除。在现实世界中,单个更新可能会影响多个表,日志,历史记录,摘要等,以及诸如上次修改日期字段,自动生成的键,计算字段之类的列。

简而言之,在真实数据库上运行测试将为您带来真实的结果,并且您不仅可以测试服务和接口,还可以测试数据库行为。您可以检查存储过程是否对数据进行了正确的处理,是否返回了预期的结果,或者您发送删除的记录是否确实被删除了!这样的测试还可能暴露诸如忘记从存储过程中引发错误以及成千上万个这样的场景这样的问题。

我认为实体框架实现的存储库模式要比到目前为止我读过的任何文章都更好。

在我们使用XBase,AdoX和Ado.Net的实体中,存储库是最佳实践! (通过存储库而不是存储库)

最后,我认为有太多人花很多时间在学习和实现存储库模式上,他们拒绝放手。主要是为了证明自己没有浪费时间。

评论


除了您不想在单元测试中测试数据库行为外,因为它完全不是那种测试级别。

– Mariusz Jamro
16 Mar 6 '16 at 13:12

是的,您在这里谈论的是集成测试,这确实很有价值,但是单元测试完全不同。您的单元测试永远不要访问真实的数据库,但是建议您添加可以的集成测试。

–克里斯·普拉特(Chris Pratt)
17年6月15日在18:52



#8 楼

我们遇到了重复但不同的Entity Framework DbContext实例的问题,当每种类型的new()向上存储库的IoC容器(例如,每个用户从DBContext调用自己的IDbSet的UserRepository和GroupRepository实例)有时可能会导致每个请求多个上下文(在MVC / Web上下文中)。

大多数情况下它仍然可以工作,但是当您在该层之上添加服务层时,这些服务假定使用一个上下文创建的对象将正确地附加为子收集到另一个上下文中的新对象时,它有时会失败,有时并不取决于提交的速度。

评论


我在几个不同的项目中都遇到过这个问题。

– ColacX
16年1月21日在10:26

#9 楼

这是由于迁移:由于连接字符串位于web.config中,因此无法进行迁移。但是,DbContext驻留在存储库层中。 IDbContextFactory需要向数据库提供配置字符串。但是,迁移仍无法从web.config获取连接字符串。

可以解决此问题,但我还没有找到一个干净的解决方案!