【问题标题】:Implementing the Repository Pattern in ASP.NET MVC在 ASP.NET MVC 中实现存储库模式
【发布时间】:2011-07-08 07:10:30
【问题描述】:

我仍然很难解决这个问题。我想像这样分离我的层(dll):

1) MyProject.Web.dll - MVC Web 应用程序(控制器、模型(编辑/视图)、视图)
2) MyProject.Services.dll - 服务层(业务逻辑)
3) MyProject.Repositories.dll - 存储库
4) MyProject.Domain.dll - POCO 类
5) MyProject.Data.dll - EF4

工作流程:

1) 控制器调用服务来获取对象以填充视图/编辑模型。
2) 服务调用存储库来获取/保存对象。
3) 存储库调用 EF 以从 SQL Server 获取/持久化对象。

我的存储库返回 IQueryable(Of T) 并在其中使用 ObjectSet(Of T)。

所以正如我所见,这些层完全取决于下一层和包含 POCO 类的库?

几个问题:

1) 现在我的存储库要与 EF 一起正常工作,它们将依赖于 System.Data.Objects,现在我的存储库层与 EF 紧密耦合,这很糟糕吗?

2) 我正在使用 UnitOfWork 模式。那应该住在哪里?它有一个作为 ObjectContext 的属性上下文,因此它也与 EF 紧密耦合。不好?

3) 我怎样才能使用 DI 使这更容易?

我希望这是一个尽可能松耦合的测试。有什么建议吗?

----------编辑----------

如果我在正确的轨道上,请告诉我。此外,因此服务被注入了一个 IRepository(Of Category) 对,它如何知道它与 EFRepository(Of T) 的具体类之间的区别? UnitOfWork 和 Service 一样吗?

一旦有人帮助我把它弄清楚到我理解的地方,我知道这似乎是微不足道的,但伙计,我有一段时间在这个问题上纠结!!

控制器

Public Class CategoryController
    Private _Service As Domain.Interfaces.IService

    Public Sub New(ByVal Service As Domain.Interfaces.IService)
        _Service = Service

    End Sub

    Function ListCategories() As ActionResult
        Dim Model As New CategoryViewModel

        Using UOW As New Repositories.EFUnitOfWork
            Mapper.Map(Of Category, CategoryViewModel)(_Service.GetCategories)
        End Using

        Return View(Model)
    End Function

End Class

服务

Public Class CategoryService

    Private Repository As Domain.Interfaces.IRepository(Of Domain.Category)
    Private UnitOfWork As Domain.Interfaces.IUnitOfWork

    Public Sub New(ByVal UnitOfWork As Domain.Interfaces.IUnitOfWork, ByVal Repository As Domain.Interfaces.IRepository(Of Domain.Category))
        UnitOfWork = UnitOfWork
        Repository = Repository

    End Sub

    Public Function GetCategories() As IEnumerable(Of Domain.Category)
        Return Repository.GetAll()
    End Function

End Class

存储库和 UnitOfWork

Public MustInherit Class RepositoryBase(Of T As Class)
    Implements Domain.Interfaces.IRepository(Of T)

End Class

Public Class EFRepository(Of T As Class)
    Inherits RepositoryBase(Of T)

End Class

Public Class EFUnitOfWork
    Implements Domain.Interfaces.IUnitOfWork

    Public Property Context As ObjectContext

    Public Sub Commit() Implements Domain.Interfaces.IUnitOfWork.Commit

    End Sub

End Class

【问题讨论】:

    标签: asp.net-mvc entity-framework-4 repository-pattern unit-of-work service-layer


    【解决方案1】:

    原答案

    1. 没有。但是,为了避免将服务与此耦合,请在您的域层中使用ISomethingRepository 接口。这将由您的 IoC 容器解决。

    2. 工作单元模式应该在您的存储库中实现。使用与我建议的将存储库与服务解耦相同的解决方案来解耦。在您的领域层中创建一个IUnitOfWorkIUnitOfWork<TContext>,并将实现放在您的存储库层中。如果所有存储库所做的只是将数据持久化到数据层中的ObjectContext,我看不出您的存储库实现需要与数据层分开的任何理由。 Repository 接口是域逻辑,但实现是数据问题

    3. 您可以使用 DI 将您的服务注入控制器,并将您的存储库注入您的服务。使用 DI,您的服务将依赖于存储库接口 ISomethingRepository,并将接收 EFSomethingRepository 的实现,而无需耦合到数据/存储库程序集。基本上,您的IControllerFactory 实现将使 IoC 容器为控制器提供所有构造函数依赖项。这将要求 IoC 容器还提供所有控制器的构造函数依赖项(服务)以及它们的构造函数依赖项(存储库)。您的所有程序集都将依赖于您的域层(具有存储库和服务接口),但不会相互依赖,因为它们依赖于接口而不是实现。您将需要一个单独的程序集来解决依赖关系,或者您需要将该代码包含在您的 Web 项目中。 (我建议单独组装)。唯一依赖于 Dependency Resolution 程序集的程序集是 UI 程序集,尽管如果您使用 IHttpModule 实现在 Application_Start 事件中注册您的依赖项,即使这也不是完全必要的(项目仍然需要一个副本您的 bin 文件夹中的 dll,但不需要项目引用)。有很多合适的开源 IoC 容器。最好的一个很大程度上取决于你选择什么。我个人喜欢 StructureMap。 it 和 Ninject 都是可靠且有据可查的 DI 框架。

    回应 Sam Striano 的编辑

    我用 VB 编码已经有好几年了,所以我的语法可能不正确。

    Public Class CategoryController
      Private _Service As Domain.Interfaces.IService
    
      'This is good.
      Public Sub New(ByVal Service As Domain.Interfaces.IService)
          _Service = Service
      End Sub
    
    
      Function ListCategories() As ActionResult
          Dim Model As New CategoryViewModel
    
    
          Using UOW As New Repositories.EFUnitOfWork
    

    这不需要在控制器中。将其移动到存储库中并使其围绕实际事务。此外,您不希望您的控制器依赖于数据层。

              Mapper.Map(Of Category, CategoryViewModel)(_Service.GetCategories)
    

    这是对 AutoMapper 的调用吗?与您的原始问题无关,但是,您应该将映射功能重新定位到 ActionFilter 以便您的返回只是 Return View(_Service.GetCategories)

          End Using
    
          Return View(Model)
      End Function
    

    Service 类没有问题。

    存储库和工作单元看起来大多不完整。您的存储库应该新建 ObjectContext 并将其注入工作单元,然后执行工作单元范围内的所有事务(类似于您在控制器中所做的)。将它放在 Controller 中的问题在于,单个服务调用可能会作用于多个工作单元。这是一篇关于如何实现工作单元的好文章。 http://martinfowler.com/eaaCatalog/unitOfWork.html。 Martin Fowler 的书籍和网站是此类主题的重要信息来源。

    【讨论】:

    • 我更新了我的答案。 (1) 是的。 (2) 是的。具体类在 MyProject.Data.dll 中,MyProject.Repositories.dll 也与 MyProject.Data.dll 合并。
    • @sam 存储库接口可以在任何地方,我经常将它们放在核心项目中,并将实现放在数据项目中
    • @dove,所以你也读过 MVC in Action 吗?如您所见,他没有“核心”项目。最接近的事情是域项目。除此之外,我们提供相同的建议。
    • @Sam Striano,我强烈建议阅读这篇关于“洋葱架构”的文章:jeffreypalermo.com/blog/the-onion-architecture-part-1。它为您的许多问题提供了深入的解释。此外,Mvc in Action 和 Mvc 2 in Action 这本书是另一个重要来源(部分由该文章的作者撰写)。
    • @Sam Striano,我真的没有时间为 SO 问题编写示例应用程序。有很多开源解决方案可以解决这个问题。我建议查看 CodeCampServer,这是我之前推荐的书的示例应用程序。我相信我已经充分回答了您的问题,但是如果您想在更深入/特定于项目的层面上讨论这个问题,可以通过 smartcaveman@gmail.com 联系我
    【解决方案2】:

    按顺序回答您的疑虑

    1) 不一定很糟糕,这取决于您坚持使用 EF 的可能性。你可以做几件事来减少这种情况。一种相对较低的成本(假设您有一些控制反转设置,如果没有跳到 3)是仅从您的服务中引用您的存储库的接口。

    2) 同样,我认为您可能会花费大量时间不使您的应用程序不与 EF 耦合,但您必须问自己,这种方向的改变是否也会对其他改变产生影响。同样,可以通过接口引入一个间接层,并在以后轻松地将一个存储库实现替换为另一个。

    3) 控制反转 应该再次允许进行您想要的所有测试。因此根本不需要很多直接引用,也不需要单独测试任何层。

    更新请求的样本。

    public class QuestionService : IQuestionService
    {
    
        private readonly IQuestionRepository _questionRepository;
    
        public QuestionService(IQuestionRepository questionRepository){
               _questionRepository = questionRepository
        }
    }
    

    因此,您的服务只知道可以在单元测试中模拟或伪造的接口。这都是非常标准的 IoC 东西。关于这方面有很多很好的参考资料,如果其中很多内容对您来说是新的,那么我会推荐一些 book 来为您提供完整的故事。

    【讨论】:

    • 您能否编辑您的答案以显示一些非常基本的代码示例?谢谢!!
    • 使用 DI,你能注入多个存储库吗?想想 CustomerService,它会有一个 CustomerRepository,然后还有一个 OrderRepository 来处理客户的订单?
    • IOC 不仅仅是测试
    • @Will Du 你是对的,但我并不是在暗示这是正确的,我是在回答他专门询问“用于测试”的问题......
    • 请你看看我上面的编辑,看看你的想法吗?
    【解决方案3】:

    我建议使用 MEF。它为您提供了您想要的依赖注入框架,但它并不成熟;它非常适合单元测试。以下是对相关问题的一些回答:Simplifying Testing through design considerations while utilizing dependency injection

    【讨论】:

      【解决方案4】:

      完整的代码示例可以在 here 找到,带有 MEF 和存储库模式(也使用 EFCodeFirst)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-30
        • 1970-01-01
        • 2011-04-19
        • 2012-06-11
        • 1970-01-01
        相关资源
        最近更新 更多