为啥存储库模式在实体框架中被广泛使用,好像它很复杂?

Posted

技术标签:

【中文标题】为啥存储库模式在实体框架中被广泛使用,好像它很复杂?【英文标题】:Why repository pattern is extensively used in entity framework as though it is complex?为什么存储库模式在实体框架中被广泛使用,好像它很复杂? 【发布时间】:2015-04-29 18:27:54 【问题描述】:

我正在创建一个演示项目,其中包含使用存储库模式和依赖注入的 crud 操作。

这是我的结构:

方法 1(非常流行,被许多开发人员使用)

我的仓库界面:

public partial interface IRepository<T>

    void Insert(T entity);

我的服务层:

public partial interface IEmployeeService

    void InsertCategory(EmployeeMaster employeeMaster);

我的类将实现该接口(服务):

public partial class EmployeeService : IEmployeeService

    private readonly IRepository<EmployeeMaster> _employeeMasterRepository;

    public EmployeeService(IRepository<EmployeeMaster> employeeMasterRepository)
    
         this._employeeMasterRepository = employeeMasterRepository;
    

   public virtual void InsertCategory(EmployeeMaster employeeMaster)
   
        _employeeMasterRepository.Insert(employeeMaster);
    

这是我的控制器:

public class HomeController : Controller

        private readonly IEmployeeService  _employeeService;

        public HomeController(IEmployeeService employeeService)
        
            this._employeeService = employeeService;
        

以上是我从 Nop Commerce 项目 (http://www.nopcommerce.com) 中遵循的结构,我认为现在大多数开发人员都在遵循这种结构,即存储库模式和依赖注入。

以前我像下面这样在我的应用程序中制作一个 Bal(业务访问层)项目,然后在 Bal 中制作类文件并像下面这样插入:

方法2

public class MyBal()

    public void Insert()
    
        using (MyEntities context = new MyEntities ())
        
            var result = context.MyTable.Add(_MyTable);
            context.SaveChanges();
            return result;
        

然后像这样直接从我的 Mvc 应用程序中调用这个方法:

[HttpPost]
public ActionResult Insert(Model M)

    MyBal bal = new MyBal ();
    bal.Insert();

那么为什么大多数开发人员继续创建如此复杂的结构,即存储库模式。谁能解释一下方法 1 和方法 2 之间的区别??

方法 1 是提高性能还是仅与代码分离或更好的代码可维护性有关。

【问题讨论】:

请注意,此问题可能被解释为主要基于意见,并且可能会被关闭。但是,我觉得这个话题足够重要,可以尝试回答。但是,我在回答中表示,这一切都取决于意见。 @Claies 我根本不认为它是主观的。显然,在方法一中使用 DI 而方法 2 不是。操作:在方法 2 中,您隐藏了对 MyBal 的依赖,这是关键的区别。 这个问题根本不是“基于意见的”——它确切地质疑 SOLID、抽象和单元测试对工程软件的基本过程有什么影响。是的,您可以使用方法 2 来做到这一点,但是一旦您质疑“我将如何测试这个?”以及“为什么 VIEW 控制器需要了解在我的系统中存储数据的数据库技术?”整个工程师的方法发生了变化。这是一个完全有效(经验)的问题。 【参考方案1】:

提供一个代码示例,说明为什么方法 1 更好地考虑一个测试场景。使用方法 2,当您在单元测试中调用 Insert 方法时,您实际上会调用:

Controller.Insert > MyBal.Insert > 数据库执行。

这不是我们在单元测试中想要的。在单元测试中,我们只想测试单元(代码块)而不是它的整个调用堆栈的对象图。

这是因为您对 MyBal 的依赖项进行了硬编码,并且无法将其切换出去。但是,如果我们使用方法 1,我们可能会:

public class HomeController : Controller

        private readonly IMyBalService  _ibalService;

        public HomeController(IMyBalService ibalService)
        
            _ibalService = ibalService;
        

        public ActionResult Insert(Model M)
        
           ibalService.Insert();
        
 

现在您可以让您的 MyBal 类实现 IBal 接口:

public class MyBal : IBalService

    public void Insert()
    
        using (MyEntities context = new MyEntities ())
        
            var result = context.MyTable.Add(_MyTable);
            context.SaveChanges();
            return result;
        

在生产场景下,一切都会像现在一样工作。但是现在正在测试中,您还可以创建 BalMockService。

public class BalMockService : IBalService

    public void Insert() 

您可以在测试时将其传递给您的 HomeController。注意我们是如何删除所有数据库调用的?测试 Controller.Insert 不需要它们。或者,我们可以让 BalMockService 返回一些测试数据,但是您有一个 void,因此不需要它。

关键是我们已将 HomeController 与 MyBal 分离,并且我们还宣传了 HomeController 需要某种 IBalService 才能运行的事实。

【讨论】:

这就是我要找的。谢谢 所以它们在这两种情况下都没有性能问题,只有代码维护和单元 ot 测试是好的。就这样吧 @MariaPithia 如果 MyBal 是一种非常昂贵的创建类型,那么使用方法 1可能会提高性能。使用方法 2,您每次点击 Home.Insert 操作时都会创建一个。使用方法 1,您可以在应用程序启动时创建一个 MyBal 实例,然后继续将其传递给 HomeController 以及其他任何需要它的地方。取决于所涉及的类型,但是是的,这可能是另一个优势,例如某种缓存。【参考方案2】:

许多人在实体框架之上实现一个存储库,它本身使用存储库模式。这背后的推理有一些变化;关于这些是否有意义的决定是一个见仁见智的问题。

    这是 Microsoft 在其教程系列 Part 9 of 10 中演示的方式。由于 Microsoft 展示了这种模式,因此被广泛接受为一种合理的做法。

    与实体框架分离。许多人努力将实体框架与他们的业务实体分开,他们的想法是,如果他们选择替换实体框架,他们可以在不修改其他业务逻辑的情况下更改存储库。然而,在实践中,您应该真正致力于一项技术,并且正确分离实体框架逻辑是一项相当多的工作。最后,您很可能会在您的存储库中获得仅存在的方法,因为它是 EF 做事的方式,因此更改为另一个 ORM 仍然会影响您的业务实体。

    泛型。将泛型与存储库模式一起使用是一种非常常见的做法,这可以最大限度地减少代码重复。想法是,如果您有数百个类来执行 CRUD 操作,那么通用存储库将更加统一和更易于维护,但灵活性可能会降低一些。

    易于与依赖注入工具集成。在某些情况下,使用存储库模式可能更容易使用依赖注入工具。

    单元测试。这与第 4 点一致,使用存储库模式为您提供了一个统一的类,可以轻松地为单元测试模拟。

这绝不是一份详尽的清单,不同意每一点的理由与同意它们的理由一样多,但这是我对围绕这一特定模式的思考过程的观察。

至于您的第二个问题,两个示例之间的区别...在第一种情况下,您创建了一个服务和一个存储库,它们可能存在于完全不同的项目或命名空间中您的企业实体。您的业​​务实体不知道或不关心实体框架。如果您需要更改您的存储库的工作方式,它应该对您的实体几乎没有影响,它的运行方式与以往相同。

在第二种情况下,您直接与实体框架绑定。这和任何其他业务实体都必须了解 Entity Framework,并且对 Entity Framework 的任何更改都可能意味着更改此实体或任何其他类似实体中的代码。对于许多实体,这可能是一个非常漫长的过程。此外,您无法轻松测试此实体,因为您执行的任何测试都会导致写入数据库。

【讨论】:

先生,您没有向我解释第一种和第二种方法之间的区别。我想知道两者在技术方面的区别 我不确定在回答题为“为什么存储库模式在实体框架中被广泛使用...”的问题时我能掌握多少技术。 一点也不遗憾。如果您在 EF 中使用 poco 类,那么我实际上是在单独的域级项目中创建域对象。 @Claies:先生,但如果你能解释一下两者之间的区别吗?? @Claies:非常感谢先生的出色回答和解释【参考方案3】:

一般来说,方法 1 优于方法 2。Clais 和 Rowan 正确地陈述了原因。

我想分享另一种与方法 1 非常相似但更通用的方法。

它极大地简化了设计和实现。基本上它与“没有特定的多个存储库”但“只有一个”的方法 1 不同。

如果您处于研究阶段,您可能需要考虑一下。我已经在answer 中用代码示例进行了解释。

【讨论】:

【参考方案4】:

有一本关于领域驱动设计的好书,其中也出现了存储库模式。这是一个很好的设计模式。

参考http://books.google.be/books/about/Domain_Driven_Design.html?id=hHBf4YxMnWMC&redir_esc=y

在那本书中,存储库用于聚合根。 围绕这种模式的想法是更好地分离关注点并遵循 SOLID 设计原则。存储库模式具有单一职责。

【讨论】:

【参考方案5】:

对于更易于维护的代码,使用接口和依赖注入提供的更松散的耦合通常是有帮助的。更具体地说,它们在进行单元测试时有很大帮助——这是首先将一些开发人员吸引到 MVC 框架的事情之一。 见:What is dependency injection?

【讨论】:

【参考方案6】:

是的。答案是更好、更易于维护的代码。

完整的答案更复杂。

虽然说它是更好且更易于维护的代码,但Repository/Service pattern 的好处仍然值得商榷,而且它不是唯一可用的模式。

解决问题的方法有时更复杂,因为问题更复杂

在学习设计模式时,我经常想知道“为什么这段代码如此复杂?他们为什么要这样做?”。

问题在于他们将复杂的解决方案应用于一个简单的问题,因为他们正在演示如何使用它(设计模式)。但是如果没有一个复杂的问题,就很难看出为什么设计模式是有益的。

您的第二种方法虽然更简单且更具可读性,但会增加coupling,并且如果应用程序变得更大,将更难维护。

【讨论】:

方法 2 如何增加耦合以及如何更难维护。您能解释一下吗?? @MariaPithia 方法 2 增加了耦合,因为您创建了一个具体类型 MyBal 的实例。方法 1 只是传入一个接口,这意味着您可以传入实现该接口的任何类型。方法 2 也很糟糕,因为您正在 ActionMethod 中创建服务 MyBal。这违反了单一职责原则,也隐藏了控制器对 MyBal 服务的依赖。那么您现在如何单独测试该方法呢?你不能。要测试 Insert 方法,您现在还要测试 MyBal,这反过来意味着测试所有 EF 代码 @rism:什么是单一职责原则?? 那是另一个问题;)您可以通过谷歌搜索“SOLID 编程”来回答。简单地说,一个班级应该有一个而且只有一个改变的理由。通过将对 MyBal 的隐藏依赖项嵌入到 HomeController 中,您现在有 2 个潜在的更改理由。 1 如果 HomeController 需要更改,可以,但 2:如果 MyBal 需要更改,则不可以。

以上是关于为啥存储库模式在实体框架中被广泛使用,好像它很复杂?的主要内容,如果未能解决你的问题,请参考以下文章

在实体框架和 Linq to Entities 中使用规范模式和表达式

具有存储库模式的实体框架 DAL、BLL

存储库模式 - 如何理解它以及它如何与“复杂”实体一起使用?

存储库模式和聚合根模式和实体框架

实体框架核心存储库模式 - 重复检查

使用带有实体框架 6 的存储库模式更新记录