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

时间:2015-02-27 05:46:35

标签: c# asp.net-mvc entity-framework repository-pattern nopcommerce

我正在创建一个演示项目,其中包含使用存储库模式和依赖注入的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;
        }
}

然后直接从My Mvc应用程序中调用此方法,如下所示:

[HttpPost]
public ActionResult Insert(Model M)
{
    MyBal bal = new MyBal ();
    bal.Insert();
}

那么为什么大多数开发人员继续创建这样一个存储库模式的复杂结构。任何人都可以解释方法1和方法2之间的区别吗?

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

6 个答案:

答案 0 :(得分:11)

许多人在Entity Framework之上实现了一个Repository,它本身使用了Repository Pattern。这背后的推理有一些变化;关于这些是否有意义的决定是一个意见问题。

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

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

  3. 泛型。将Generics与存储库模式一起使用是一种非常常见的做法,可以最大限度地减少代码重复。我们的想法是,如果你有数百个类来执行CRUD操作,那么Generic Repository将更加统一且更易于维护,但可能灵活性稍差。

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

  5. 单元测试。这与第4点一致,使用Repository Pattern为您提供了一个统一的类,可以轻松地模拟单元测试。

  6. 这绝不是一个详尽的清单,并且有很多理由不同意每一点同意它们,但这是我对围绕这种特定模式的思考过程的观察。

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

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

答案 1 :(得分:5)

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

Controller.Insert&gt; MyBal.Insert&gt;数据库执行。

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

这是因为您已经对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返回一些测试数据,但是你有一个空白所以不需要它。

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

答案 2 :(得分:3)

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

完整的答案更复杂。

说过它是更好,更易维护的代码,Repository/Service pattern的好处仍有争议,而且它不是唯一可用的模式。

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

在了解设计模式的同时,我常常想知道&#34;为什么这段代码如此复杂?他们为什么这样做?&#34;。

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

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

答案 3 :(得分:1)

对于更易于维护的代码,通过使用接口和依赖注入提供的更松散的耦合通常是有帮助的。更具体地说,它们在进行单元测试时提供了很多帮助 - 这是将开发人员首先引入MVC框架的一个方面。 请参阅:What is dependency injection?

答案 4 :(得分:1)

有一本关于域驱动设计的好书,并且存储库模式也出现在那里。这是一个很好的设计模式。

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

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

答案 5 :(得分:1)

一般而言,方法1优于方法2.克莱斯和罗文正确地说明了原因。

我想分享另一种与方法1非常类似的方法,但它是一个更通用的版本。

它极大地简化了设计和实施。基本上它从“没有特定的多个存储库”而不是“只有一个”的方法1中推迟。

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