为什么存储库模式在 entity framework 中被广泛使用,好像它很复杂?

Why repository pattern is extensively used in entity framework as though it is complex?

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

这是我的结构:

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

我的存储库界面:

public partial interface IRepository<T>
{
    void Insert(T entity);
}

我的服务层:

public partial interface IEmployeeService
{
    void InsertCategory(EmployeeMaster employeeMaster);
}

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

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 中制作 class 文件并像下面那样插入:

方法 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();
}

那么为什么大多数开发人员继续创建如此复杂的存储库结构pattern.Can有人向我解释方法 1 和方法 2 之间的区别吗??

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

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

完整答案比较复杂。

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

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

在学习设计模式的过程中,我经常会疑惑"Why is this code so complicated? Why are they doing it this way?"。

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

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

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

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

  2. 与Entity Framework分离。许多人努力将 Entity Framework 从他们的业务实体中分离出来,他们认为如果他们选择替换 Entity Framework,他们可以在不修改其他业务逻辑的情况下更改存储库。然而,在实践中,您应该真正致力于一项技术,正确分离 Entity Framework 逻辑需要大量工作。最后,您很可能会在存储库中得到仅因为它是 EF 做事方式而存在的方法,因此更改为另一个 ORM 仍会影响您的业务实体。

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

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

  5. 单元测试。这与第 4 点一致,使用存储库模式为您提供了一个统一的 class,可以很容易地模拟单元测试。

这绝不是一个详尽的列表,不同意每个观点的理由和同意它们的理由一样多,但这是我对围绕这一特定模式的思维过程的观察。

关于你的第二个问题,两个例子之间的区别...在第一种情况下,你创建了一个服务和一个存储库,它们可能完全存在于一个与您的业务实体不同的项目或名称空间。您的企业实体不知道,也不关心 Entity Framework。如果您需要更改您的存储库的工作方式,它应该对您的实体几乎没有影响,它会像往常一样运作。

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

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

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

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

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

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

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

它极大地简化了设计和实现。基本上它推迟了 "not having specific multiple repository" 中的方法 1,但 "have just one".

如果您处于研究阶段,您可能需要考虑一下。我已经在 .

中用代码示例对其进行了解释

提供代码示例,说明为什么方法 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 class 实现 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 return 一些测试数据,但是你有一个空白所以不需要它。

重点是我们已经将 HomeController 与 MyBal 分离,并且我们还公布了 HomeController 需要某种 到 运行 的 IBalService 的事实。