为啥使用存储库模式或者请向我解释一下?
Posted
技术标签:
【中文标题】为啥使用存储库模式或者请向我解释一下?【英文标题】:Why use Repository Pattern or please explain it to me?为什么使用存储库模式或者请向我解释一下? 【发布时间】:2012-02-03 16:04:48 【问题描述】:我正在学习存储库模式,并且正在阅读 Repository Pattern with Entity Framework 4.1 and Code First 和 Generic Repository Pattern - Entity Framework, ASP.NET MVC and Unit Testing Triangle 了解他们如何使用实体框架实现存储库模式。
说
•对上层隐藏EF •使代码更易于测试
让代码更易测试我明白,但为什么要对上层隐藏 EF?
看看他们的实现,它似乎只是用一个通用的方法来包装实体框架来查询实体框架。其实这样做的原因是什么?
我假设是为了
-
松耦合(这就是为什么对上层隐藏 EF?)
避免为同一查询重复编写相同的 LINQ 语句
我理解正确吗?
如果我写一个 DataAccessLayer 这是一个类有方法
QueryFooObject(int id)
..//query foo from entity framework
AddFooObject(Foo obj)
.. //add foo to entity framework
......
QueryBarObject(int id)
..
AddBarObject(Bar obj)
...
这也是存储库模式吗?
对假人的解释会很棒:)
【问题讨论】:
"从上层隐藏 EF" 目标比隐藏/抽象 EF 稍微宽一些。这是为了消除与持久性(或数据)层的紧密耦合。该应用程序对数据的处理方式(ADO、EF、Web API 或仅用于单元测试的模拟数据)视而不见。应用程序在运行时从应用程序的配置中获取“一个”数据存储库注入其控制器。因此,交换数据层就像更改应用程序的配置一样简单。我将存储库保存在一个单独的项目中,这使得 UI 项目的重量和数据不可知。 【参考方案1】:我认为你不应该这样做。
实体框架已经是您数据库上的一个抽象层。上下文使用工作单元模式,每个 DBSet 都是一个存储库。在此基础上添加存储库模式会使您远离 ORM 的功能。
我在博文中谈到了这一点: http://www.nogginbox.co.uk/blog/do-we-need-the-repository-pattern
添加您自己的存储库实现的主要原因是您可以使用依赖注入并使您的代码更具可测试性。
EF 开箱即用的可测试性不是很好,但使用可以注入的接口制作 EF 数据上下文的可模拟版本非常容易。
我在这里谈到过: http://www.nogginbox.co.uk/blog/mocking-entity-framework-data-context
如果我们不需要存储库模式来使 EF 可测试,那么我认为我们根本不需要它。
【讨论】:
我真的很喜欢你博文中的这句话:“这个抽象层可以让你远离你的 ORM 的特征。”有人可能会说,这个“距离”是存储库的目的。但是对于人们在这里提出的关于 repo+EF 的许多问题,我感觉他们从抽象开始,而对具体的功能了解不够。抽象从具体事物开始,而不是相反,实际上您必须了解不止一个 事物(不仅仅是 EF)才能构建有意义的抽象。如果他只看到过狗而没有看到过猫,那么没有人会想到动物。 我同意。我一直使用存储库模式,因为这就是我被教导这样做的方式。但最近我意识到,对于 90% 的用例来说,这只是不必要的抽象。在我的上一个项目中,我只是为 dbContext 类创建了一个接口,该接口公开了表、savechanges 函数以及我可能需要的任何其他附加功能。 存储库抽象还有另一个目的。它抽象了为您查询/创建数据的方式。例如,如果您需要从数据库中的其他数据以外的其他数据构建实体,该怎么办?使用存储库的层不会改变,也不会知道它接收到的数据是在哪里以及如何构建的。 我不完全同意。 EF 是您如何访问数据的一个非常具体的实现。在应用程序和数据访问之间进行抽象是至关重要的。如果您决定更改 ORM 或像 @eranotzap 建议的那样拥有多个数据源怎么办?尽管 EF 的可测试性不高,但它不应该是对 EF 使用抽象的唯一原因。 @DDiVita 您实际上多久更改一次您正在使用的 ORM?【参考方案2】:这张图很容易理解
【讨论】:
EF 中的 db 上下文遵循工作单元模式,每个集合都类似于一个存储库。您可以围绕上下文创建一个非常简单的包装器,并且仍然可以进行单元测试。【参考方案3】:一件事是提高可测试性并与底层持久性技术松散耦合。但是每个聚合根对象也将有一个存储库(例如,订单可以是聚合根,它也有订单行(不是聚合根),以使域对象持久性更加通用。
这也使管理对象变得更加容易,因为当您保存订单时,它也会保存您的子项目(可以是订单行)。
【讨论】:
嗯,我仍然不明白为什么每个聚合根对象部分都有一个存储库。当我使用实体框架查询订单对象时,订单不会包含订单行列表......?对不起,我越来越糊涂了…… 在 EF 中,您还可以使用 ObjectContext.SaveChanges() 方法保存和检索完整的聚合根对象。但我写它是因为它是存储库模式的优势之一。 我明白了,现在我明白了。谢谢。 遇到这种情况的人应该知道存储库模式是一种反模式。 Ayende 解释了原因:youtube.com/watch?v=0tlMTJDKiug @SamDev 谢谢 - 我花了好几个小时在网上冲浪,才找到能解释为什么我不断看到的存储库代码的激增让我感到恶心的人。【参考方案4】:将查询放在中心位置也是一个优势;否则您的查询分散在各处,更难维护。
还有你提到的第一点:“隐藏EF”是件好事!例如,保存逻辑可能难以实现。有多种策略最适用于不同的场景。尤其是在保存相关实体也发生变化的实体时。
使用存储库(结合 UnitOfWork)也可以集中这个逻辑。
Here 是一些带有很好解释的视频。
【讨论】:
【参考方案5】:存储库系统非常适合测试。
一个原因是您可以使用依赖注入。
基本上,您为存储库创建一个接口,并在创建对象时为其引用该接口。然后您可以稍后制作一个实现该接口的假对象(例如使用 moq)。使用类似 ninject 的东西,您可以将正确的类型绑定到该接口。繁荣,您刚刚从等式中消除了依赖关系,并将其替换为可测试的东西。
这个想法是为了能够轻松地交换对象的实现以用于测试目的 希望这是有道理的。
【讨论】:
【参考方案6】:与您不在应用中对文件路径进行硬编码的原因相同:loose coupling 和 encapsulation。想象一个应用程序包含对“c:\windows\fonts”的硬编码引用以及可能导致的问题。您不应该对路径的引用进行硬编码,那么为什么要对持久层的引用进行硬编码呢?将您的路径隐藏在配置设置(或special folders 或您的操作系统支持的任何内容)后面,并将您的持久性隐藏在存储库后面。如果持久性问题隐藏在存储库后面,那么单元测试、部署到其他环境、交换实现以及对域对象的推理会容易得多。
【讨论】:
【参考方案7】:当您将存储库类设计为类似于域对象、为所有存储库提供相同的数据上下文并促进工作单元的实施时,存储库模式是有意义的。请在下面找到一些人为的示例。
class StudenRepository
dbcontext ctx;
StundentRepository(dbcontext ctx)
this.ctx=ctx;
public void EnrollCourse(int courseId)
this.ctx.Students.Add(new Course()CourseId=courseId);
class TeacherRepository
dbcontext ctx;
TeacherRepository(dbcontext ctx)
this.ctx=ctx;
public void EngageCourse(int courseId)
this.ctx.Teachers.Add(new Course()CourseId=courseId);
public class MyunitOfWork
dbcontext ctx;
private StudentRepository _studentRepository;
private TeacherRepository _teacherRepository;
public MyunitOfWork(dbcontext ctx)
this.ctx=ctx;
public StudentRepository StundetRepository
get
if(_studentRepository==null)
_stundentRepository=new StundetRepository(this.ctx);
return _stundentRepository;
public TeacherRepository TeacherRepository
get
if(_teacherRepository==null)
_teacherRepository=new TeacherRepository (this.ctx);
return _teacherRepository;
public void Commit()
this.ctx.SaveChanges();
//some controller method
public void Register(int courseId)
using(var uw=new MyunitOfWork(new context())
uw.StudentRepository.EnrollCourse(courseId);
uw.TeacherRepository.EngageCourse(courseId);
uw.Commit();
【讨论】:
你应该解释你上面写的代码。【参考方案8】:我知道在这里提供答案链接不好,但是想分享视频,它解释了存储库模式在与实体框架一起使用时的各种优点。下面是youtube的链接。
https://www.youtube.com/watch?v=rtXpYpZdOzM
它还提供了有关如何正确实现存储库模式的详细信息。
【讨论】:
您不再需要实体框架核心中的存储库模式进行测试,除非您想从业务层隐藏 ef 实现。以上是关于为啥使用存储库模式或者请向我解释一下?的主要内容,如果未能解决你的问题,请参考以下文章
请向我解释错误的性质:由于未捕获的异常“NSInvalidArgumentException”而终止应用程序