在 ASP.NET MVC 中实现存储库模式

发布于 2024-10-20 07:44:59 字数 2477 浏览 1 评论 0原文

我仍然很难理解这一点。我想像这样分离我的层(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) 的具体类之间的区别?与工作单元和服务相同吗?

一旦有人帮助我弄清楚了我所理解的问题,我知道这看起来微不足道,但是我真的很难理解这个问题!

控制器

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

I am still having a hard time wrapping my head around this. I want to separate my layers (dlls) like so:

1) MyProject.Web.dll - MVC Web App (Controllers, Models (Edit/View), Views)
2) MyProject.Services.dll - Service Layer (Business Logic)
3) MyProject.Repositories.dll - Repositories
4) MyProject.Domain.dll - POCO Classes
5) MyProject.Data.dll - EF4

Workflow:

1) Controllers call Services to get objects to populate View/Edit Models.
2) Services call Repositories to get/persist objects.
3) Repositories call EF to get/persist objects to and from SQL Server.

My Repositories return IQueryable(Of T) and inside them they utilize ObjectSet(Of T).

So as I see this, the layers depend on exactly the next layer down and the lib that contains the POCO classes?

A few concerns:

1) Now for my Repositories to work correctly with EF, they will depend on System.Data.Objects, now I have a tight coupling with EF in my repository layer, is that bad?

2) I am using the UnitOfWork pattern. Where should that live? It has a Property Context As ObjectContext, so that is tightly coupled to EF as well. Bad?

3) How can i use DI to make this easier?

I want this to be a loosely coupled as possible for testing. Any suggestions?

---------- Edit ----------

Please let me know if I am on the right track here. Also, so the Service gets injected with an IRepository(Of Category) right, how does it know the difference between that and the concrete class of EFRepository(Of T)? Same with the UnitOfWork and the Service?

Once someone helps me figure this out to where I understand it, I know it will have seemed trivial, but man I am having a heck of a time wrapping my head around this!!

Controller

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

Service

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

Repository and 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

浅暮の光 2024-10-27 07:44:59

原始答案

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

  2. 工作单元模式应该通过您的存储库来实现。使用与我建议的将存储库与服务解耦相同的解决方案来解耦。在域层中创建 IUnitOfWorkIUnitOfWork,并将实现放入存储库层中。如果存储库所做的只是将数据保存到数据层中的ObjectContext,我不认为您的存储库实现需要与数据层分开。 存储库接口是领域逻辑,但实现是数据问题

  3. 您可以使用 DI 将服务注入控制器,并将存储库注入服务。通过 DI,您的服务将依赖于存储库接口 ISomethingRepository,并且将接收 EFSomethingRepository 的实现,而无需耦合到数据/存储库程序集。基本上,您的 IControllerFactory 实现将使 IoC 容器为控制器提供所有构造函数依赖项。这将要求 IoC 容器还提供所有控制器的构造函数依赖项(服务)及其构造函数依赖项(存储库)。所有程序集都将依赖于域层(具有存储库和服务接口),但不会相互依赖,因为它们依赖于接口而不是实现。您要么需要一个单独的程序集来进行依赖关系解析,要么需要将该代码包含在您的 Web 项目中。 (我建议单独组装)。唯一依赖于依赖项解析程序集的程序集将是 UI 程序集,尽管如果您使用 IHttpModule 实现在 Application_Start 处注册依赖项,那么这也不是完全必要的。 > 事件(项目仍需要 bin 文件夹中的 dll 副本,但不需要项目引用)。有很多合适的开源 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

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

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

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

      End Using

      Return View(Model)
  End Function

Service 类没有问题。

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

Original Answer

  1. No. However, to avoid coupling the Services to this, have an ISomethingRepository interface in your domain layer. This will be resolved by your IoC container.

  2. The Unit of Work patterns should be implemented with your Repositories. Use the same solution to decoupling this as I suggested with decoupling your repositories from your services. Create an IUnitOfWork or IUnitOfWork<TContext> in your domain layer, and put the implementation in your Repository layer. I don't see any reason that your repository implementation needs to be separate from your Data layer, if all the Repositories do is persist data to the ObjectContext in data layer. The Repository interface is domain logic, but the implementation is a data concern

  3. You can use DI to inject your services into the controllers and your repositories into your services. With DI, your service will have a dependency on the repository interface ISomethingRepository, and will receive the implementation of the EFSomethingRepository without being coupled to the data/repository assembly. Basically, your IControllerFactory implementation will get the IoC container to provide all the constructor dependencies for the Controller. This will require that the IoC container also provides all the controllers' constructor dependencies (service) their constructor dependencies (repositories) as well. All of your assemblies will have a dependency on your domain layer, (which has the repository and service interfaces), but will not have dependencies on each other, because they are dependent on the interface and not the implementation. You will either need a separate assembly for the Dependency Resolution or you will need to include that code in your Web project. ( I would recommend a separate assembly). The only assembly with a dependency on the Dependency Resolution assembly will be the UI assembly, although even this is not completely necessary if you use an IHttpModule implementation to register your dependencies at the Application_Start event (the project will still need a copy of the dll in your bin folder, but a project reference is not necessary). There are plenty of suitable open source IoC containers. The best one depends a lot on what you choose. I personally like StructureMap. Both it, and Ninject are reliable and well documented DI frameworks.

Response to Sam Striano's Edits

It's been years since I've coded in VB so my syntax may be off.

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

This doesn't need to be in the controller. Move it into the Repository and have it surround the actual transaction. Also, you don't want your controller to have a dependency on the data layer.

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

Is this a call to AutoMapper? Not related to your original question, but, you should relocate the mapping functionality to an ActionFilter so your return is just Return View(_Service.GetCategories)

      End Using

      Return View(Model)
  End Function

The Service class had no problems.

The Repository and Unit of Work look mostly incomplete. Your Repository should new up the ObjectContext and inject it into the Unit of Work, then execute all transactions in the scope of the Unit of Work (similar to what you did in the controller). The problem with having it in the Controller is it's possible that a single Service call could be scoped to multiple units of work. Here is a good article on how to implement Unit of Work. http://martinfowler.com/eaaCatalog/unitOfWork.html. Martin Fowler's books and website are great sources of information on these types of topics.

一城柳絮吹成雪 2024-10-27 07:44:59

按顺序回答您的问题

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

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

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

更新所请求的样品。

public class QuestionService : IQuestionService
{

    private readonly IQuestionRepository _questionRepository;

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

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

To answer your concerns in order

1) Not necessarily bad, kind of depends on how likely you are to stick with EF. There are several things you could do to reduce this. One relatively low cost (assuming you have some Inversion of Control setup, if not skip to 3) is to only reference interfaces of your repositories from your services.

2) Same again, I think you could spend a lot of time not making your application not coupled to EF but you have to ask yourself if this change of direction would not make for other changes as well. Again, a layer of indirection could be brought in through interfacing and easily swap out one repository implementation with another later.

3) Inversion of Control should again allow all the testing you'd want. Thus no need for many direct references at all and to test any layer in isolation.

UPDATE for requested sample.

public class QuestionService : IQuestionService
{

    private readonly IQuestionRepository _questionRepository;

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

Thus your service only knows of an interface which can be mocked or faked within your unit tests. It is all pretty standard IoC stuff. There is lots of good reference out there on this, if a lot of this is new to you then I'd recommend some a book to give you the full story.

又怨 2024-10-27 07:44:59

我建议使用 MEF。它为您提供了您想要的依赖注入框架,但它并不成熟;它非常适合单元测试。以下是相关问题的一些答案:通过设计简化测试使用依赖注入时的注意事项

I would suggest using MEF. It gives you the dependency injection framework you want but it isn't full-fledged; it's excellent for unit test. Here are a few answers to a related question: Simplifying Testing through design considerations while utilizing dependency injection

§对你不离不弃 2024-10-27 07:44:59

Full code exmple can be found here with MEF and Repository Pattern (also uses EFCodeFirst).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文