在ASP.NET MVC中实现仓储模式

7
我仍然很难理解这个问题。我想要将我的层(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,现在我的存储库层中有一个紧密的耦合,这是不好的吗?
2) 我正在使用 UnitOfWork 模式。它应该放在哪里?它具有属性 Context As 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

仓储和工作单元

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
4个回答

7

原文回答

  1. 不需要。但是,为了避免将服务与此耦合,可以在领域层中使用一个ISomethingRepository接口。这将由您的IoC容器解析。

  2. 应该在存储库中实现工作单元模式。使用与我建议将存储库与服务分离的相同解决方案来解耦。在领域层中创建IUnitOfWorkIUnitOfWork<TContext>,并将其实现放在存储库层中。如果所有存储库所做的只是将数据持久化到数据层中的ObjectContext中,则我认为您不需要将存储库实现与数据层分开。 存储库接口是领域逻辑,但实现是数据关注点

  3. 您可以使用DI将服务注入控制器中,将存储库注入您的服务中。使用依赖项注入(DI),您的服务将依赖于存储库接口ISomethingRepository,并将接收EFSomethingRepository的实现,而不会与数据/存储库程序集耦合。基本上,您的IControllerFactory实现将获取IoC容器为控制器提供所有构造函数依赖项。这将需要IoC容器提供所有控制器的构造函数依赖项(服务)和它们的构造函数依赖项(存储库)。所有程序集都将依赖于您的领域层(具有存储库和服务接口),但不会相互依赖,因为它们依赖于接口而不是实现。您将需要一个独立的程序集来进行依赖关系解析或者您将需要将该代码包含在Web项目中。(我建议使用一个独立的程序集)。唯一依赖于依赖关系解析程序集的程序集将是UI程序集,尽管如果您使用IHttpModule实现在事件中注册依赖项,则甚至这也不完全必要(项目仍然需要一个dll副本在bin文件夹中,但不需要项目引用)。有许多合适的开源IoC容器。最好的取决于您选择的内容。我个人喜欢StructureMap。它和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

这段话的意思是:“这不需要在控制器中。将它移动到Repository中,并将其包围在实际事务周围。此外,您不希望控制器依赖数据层。”
          Mapper.Map(Of Category, CategoryViewModel)(_Service.GetCategories)

这是对AutoMapper的调用吗?虽然与您最初的问题无关,但您应该将映射功能重定位到ActionFilter中,以便您的返回值只是Return View(_Service.GetCategories)。
      End Using

      Return View(Model)
  End Function

Service类没有问题。

Repository和Unit of Work看起来大部分都不完整。您的Repository应该新建ObjectContext并将其注入到Unit of Work中,然后在Unit of Work的范围内执行所有事务(类似于您在控制器中所做的)。将其放在控制器中的问题是可能会将单个Service调用作用域限定为多个工作单元。 这是一篇关于如何实现Unit of Work的好文章:http://martinfowler.com/eaaCatalog/unitOfWork.html。马丁·福勒(Martin Fowler)的书籍和网站是这些主题的重要信息来源。


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

1

为了回答你的问题:

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

2)同样,我认为你可以花很多时间使你的应用程序不与EF耦合,但你必须问自己,这个方向的改变是否会带来其他改变。同样,通过接口引入一层间接性,稍后可以轻松地交换一个存储库实现为另一个存储库实现。

3)控制反转再次允许您进行所需的所有测试。因此,根本不需要许多直接引用,并且可以单独测试任何一层。

请求示例的更新。

public class QuestionService : IQuestionService
{

    private readonly IQuestionRepository _questionRepository;

    public QuestionService(IQuestionRepository questionRepository){
           _questionRepository = questionRepository
    }
}

因此,您的服务只知道可以在单元测试中模拟或伪造的接口。这都是相当标准的IoC(控制反转)内容。如果您对其中大部分内容感到陌生,那么我建议您阅读一本书籍,以获得完整的故事。

你能否编辑你的回答,展示一些非常基本的代码示例吗?谢谢! - Sam
使用依赖注入,您能注入多个仓储库吗?想象一下一个CustomerService,它将有一个CustomerRepository,还有一个OrderRepository来处理客户的订单? - Sam
IOC 不仅仅是关于测试的。 - Will Du
@Will Du,你说得对,但我并不是在暗示它是这样的,我只是在回答他的问题,他特别问到“用于测试”... - dove
请您看一下我上面的编辑,并说出您的想法? - Sam

0

完整的代码示例可以在这里找到,使用MEF和Repository Pattern(还使用了EFCodeFirst)。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接