11

I are planning migrate our data access layer to using repository pattern and unit of work.

I do know repository will help me to change persistence store (database, collection...etc) and technology such as EF to MongoDB easily. So I noticed some key points of implementation of a repository such as:

  1. Return IEnumerable instead of IQueryable
  2. Repository should take responsibilities for CRUD operations only
  3. Return type of repository method should be model (entity)
  4. Only implement repository for aggregate root

If I apply these key points during implement repository in my project, I totally lost how to deal with complex query in which related to multiple entities.

Currently what I already had was that on BLL library with a lot of services class will contact directly to DbContext and DbSet of EF and some of validation like this:

public IEnumerable<ProjectDTO> GetProjectWithDetails()
{
    // Validation

    // Logging

    // Can be any logic need to before query data.  

    Dbcontext.Projects.Where(p => 
    // multiple of conditions go here follow business rules
    // conditions will need to check another entities (task, phase, employee...) such as:
    // 1. project have task status 'in-progress' .. etc
    // 2. project have employeeid 1,2,3..
    // 3. project have stask start at some specific date.
    // 4....    
    )
    .Select(p => new ProjectDTO
    {
        Label = p.Label,
        Phase = new PhaseDTO{
            Label = p.Phase.Label,
            Tasks = p.Phase.Tasks.Select(t => new TaskDTO{
                // some related properties
            })
        }
    }).ToList();
} 

I am currently using Data Transfer Object (DTO) to be the middle classes between model and viewmodel on controller and using Mapper to map properties.

If I keep key notes on repository above I need to do multiple round trip to database for getting data, and it will return whole model instead of useful columns. But if I migrate those kind of methods to repository I will broken repository pattern because it will contain business logic and return type not an model.

So question is what should I do in this case? Please give me some advise to put me on the right track.

Many thanks.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
vietvoquoc
  • 813
  • 1
  • 10
  • 23
  • You can return `IEnumerable` in the repository methods, but I don't think there's anything wrong with `IQueryable` either (it's part of the .NET BCL the same way `IEnumerable` is). But even if you want to return `IEnumerable`, you can use an `IQueryable` for the actual return value, since it extends `IEnumerable`. So there is no need to call `ToList()` in your example. Here's a similar question: https://stackoverflow.com/questions/42733341/how-to-use-joins-with-generic-repository-pattern-entity-framework/42736117#42736117 – Akos Nagy May 13 '18 at 09:30
  • @AkosNagy you mean that just expose `IQueryable` from repository and let service layer do what it need to do? – vietvoquoc May 14 '18 at 06:43
  • Basically yes. The queries that you need to create are usually the results of some business requirement (i.e. filter tasks that are overdue), so they should be in the business logic, not in the domain agnostic repositories. The only 'drawback' is that since the `IQueryable` is returned from the repositories, you do need to call `ToList()` in the business logic layer, and to be honest, that leaks the abstraction a bit. But `IQueryable` and deferred execution is so integrated into .NET, that I don't see this as a very serious issue. – Akos Nagy May 14 '18 at 06:51

2 Answers2

9

This depends on opinion and the use case, but I personally do not agree with some of the key points you mentioned.

Return IEnumerable instead of IQueryable

Agree. Returning IQueryable defeats basic purpose of existence of Repository. There are lot many articles on net explaining how this creates more problem than a solution. Though, I have learned to never say never. Refer this, this, or this. Or simply search google.

Repository should take responsibilities for CRUD operations only

Agree. With simple CRUD, it may also do complex reads and writes. My experience tell that in exceptional cases, you have to put a part of business logic in repository if you want to implement it on RDBMS side. This is not right or wrong. If you know what you are doing, there should not be an issue.

Return type of repository method should be model (entity)

If you are not using DDD, then yes. Otherwise, it is implementation decision. With full ORM like EF or NHibernate, it is better to return Domain Model directly instead of per table Entity instance.

It is always suggested that Repository should return Domain Model. That way, mapping of data returned from RDBMS with the Domain Model (and vice versa) becomes responsibility of repository. This avoids necessity of leaking the persistence concerns outside the repository and thus makes your rest of the application persistence ignorant.

But, not every application implement DDD. Many small applications design entities those are mapped 1 to 1 with their database design. In this case, repository may return entity (which is equivalent to your table and fields) itself and mapping becomes responsibility of calling code. Or, repository may map the necessary model and return the model itself. This is strongly discouraged because the problems stated above. With this, you have to give up some of the features full ORMs provide.

All this depends on what is your problem, what are your design objectives, size of application and other design patterns implemented etc. That is why it becomes design decision.

Only implement repository for aggregate root

Agreed if it is with DDD. If not, multiple choices are available like per table repository. Again, depends on use case.

About complex query

It is not necessary that repositories should only implement simple CRUD methods. It may also return complex object graph. It may do complex querying. That said, with simple methods like Get, GetById etc, it may also consume complex methods like GetTopBrokenVehicles(vehicleType, top). It is absolutely fine if you write separate method for complex query.

Challenge is that, how you accept the necessary parameters. You may accept the parameters inline or build separate simple input parameter class.

Here is sample code for Repository and UoW.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
3

One of benefit of using Repository Pattern is hiding complex queries, you should see repository as collection of objects in memory (martin fowler):

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers https://martinfowler.com/eaaCatalog/repository.html

Mehdi Payervand
  • 251
  • 3
  • 11