问题
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
回答1:
Original Answer
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.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
orIUnitOfWork<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 theObjectContext
in data layer. The Repository interface is domain logic, but the implementation is a data concernYou 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 theEFSomethingRepository
without being coupled to the data/repository assembly. Basically, yourIControllerFactory
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 anIHttpModule
implementation to register your dependencies at theApplication_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.
回答2:
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.
回答3:
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
回答4:
Full code exmple can be found here with MEF and Repository Pattern (also uses EFCodeFirst).
来源:https://stackoverflow.com/questions/5185220/implementing-the-repository-pattern-in-asp-net-mvc