2

I have started translating a project into a generic repository and unit of work pattern. So far I have been able to reverse engineer all direct context references in the controllers to a generic repository; however, I am having trouble with the following two lines of codes:

`context.Entry(ticket).Collection(i => i.TicketItems).Load();
            ticket.TicketItems.Clear();`

This is what my controller was doing before to remove any reference between a Ticket and a TicketItem. There is a many-to-many relation between Ticket and TicketItem. So those two lines of code is what I was using before to remove all TicketItems from a Ticket

SOfanatic
  • 5,523
  • 5
  • 36
  • 57

1 Answers1

5

You could have two methods in the repository interface - one for navigation references and one for navigation collections:

public interface IRepository<T>
{
    void LoadNavigationReference<TReference>(T entity,
        Expression<Func<T, TReference>> navigationProperty,
        params Expression<Func<TReference, object>>[] includes)
        where TReference : class;

    void LoadNavigationCollection<TElement>(T entity,
        Expression<Func<T, ICollection<TElement>>> navigationProperty,
        params Expression<Func<TElement, object>>[] includes)
        where TElement : class;
}

They are supposed to support including other nested navigation properties as well. The implementation would be:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly MyContext _dbContext;

    public Repository(MyContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void LoadNavigationReference<TReference>(T entity,
        Expression<Func<T, TReference>> navigationProperty,
        params Expression<Func<TReference, object>>[] includes)
        where TReference : class
    {
        if (includes == null || includes.Length == 0)
            _dbContext.Entry(entity).Reference(navigationProperty).Load();
        else
            _dbContext.Entry(entity).Reference(navigationProperty).Query()
                .IncludeMultiple(includes).Load();
    }

    public void LoadNavigationCollection<TElement>(T entity,
        Expression<Func<T, ICollection<TElement>>> navigationProperty,
        params Expression<Func<TElement, object>>[] includes)
        where TElement : class
    {
        if (includes == null || includes.Length == 0)
            _dbContext.Entry(entity).Collection(navigationProperty).Load();
        else
            _dbContext.Entry(entity).Collection(navigationProperty).Query()
                .IncludeMultiple(includes).Load();
    }
}

The IncludeMultiple extension method used above is taken from Ladislav Mrnka's answer here.

The example in your question would then look like this:

repository.LoadNavigationCollection(ticket, i => i.TicketItems);
ticket.TicketItems.Clear();

where repository is of type IRepository<Ticket>.

If TicketItem had another navigation property, say TicketItemDetails, you could eagerly load it together with the TicketItems this way:

repository.LoadNavigationCollection(ticket, i => i.TicketItems,
    t => t.TicketItemDetails);

Edit

BTW as critical side note about Generic Repositories: The above is part of a generic repository that has actually 16 methods and that I have used in an early stage of a project before I have stopped to extend it and abandoned this style completely.

The repository had around 5 methods in the beginning (like most of the usual repositories you see on the internet). It was impossible to work with only those 5 methods without losing a lot of Entity Framework's power. So, I needed to extend it step by step, driven by actual requirements in the project, and it never became "complete" before I removed it from the project.

The problem is: If you would show the interface to someone ("here I have a super generic and technology independent data access interface") he would say immediately "aha, you are using Entity Framework!". The reason is that almost every method is just a wrapper around an Entity Framework method and you can't hide that by using other names for the interface methods. The whole interface smells of EF DbContext/Code-First.

Now, try to implement that interface with another technology than Entity Framework. Most likely you'll run into the same problem that I had: Lots of methods are missing to leverage the power of that other technology, or the existing methods have wrong parameters or there are too many methods you can't reasonably implement with the other technology.

I even failed and lost all fun to build an in-memory implementation of that interface for unit testing.

In my opinion such a Generic Repository is a typical example of a Leaky Abstraction where the real implementation you have in mind shines through the whole interface.

But if you can't abstract the usage of Entity Framework away, building a generic repository interface is rather pointless.

Community
  • 1
  • 1
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • Thank you for you answer and your input. I am new to this whole generic repository and unit of work pattern, but from your experience, it would be better to use entity framework directly? But how would I go about keeping my controllers short? do you think it'd be OK to stick to the generic repository so long I don't have to create that many methods? – SOfanatic Apr 05 '13 at 23:49
  • I love your comment about Repository pattern and ef. Just dont do it. Repository is from 2004 or sth when orms werent so popular. Also original repository had only find, add, remove, save methods. – Piotr Perak Apr 06 '13 at 00:05
  • 2
    @Luis: It's not that I'd recommend to use EF and `context` directly in the controller. Separation of concerns is a good practice but doesn't require always abstractions. What I do today is more business logic focussed. In your example I probably had a kind of "`TicketService`" class or interface with a method `ClearTicket` and I'd call `ticketService.ClearTicket(ticket)`. But in this method I use EF directly without repo. It moves the whole business logic out of the controller, not only the data access. The whole subject is partially a matter of taste, you must find your own best practice :) – Slauma Apr 06 '13 at 00:11
  • @Peri Repository is a very good pattern that is unrelated to an ORM. An orm is an implementation detail of the repository. That being said, in this case, the Repository is useless because it exposes how EF works i.e it's just a leaky abstraction. Can you switch the implementation of the repo to use a NOSQl db or a remote service without touching the interface? – MikeSW Apr 06 '13 at 08:29
  • Read your comments on generic repositories and shouted YES! at every word. (Glad I was alone). Bookmarked it as a well-expressed developer experience. – Gert Arnold Apr 06 '13 at 11:02
  • @MikeSW: I'm saying repository with ORM is nonsense not repository in general. – Piotr Perak Apr 06 '13 at 18:11
  • @Peri I'm saying that Repository and ORM work together very well. The only case when a repo brings no benefits is when it's a CRUD app and you work directly with the db structure – MikeSW Apr 07 '13 at 10:30
  • @MikeSW: Show me repository pattern implementation that hides ORM and doesn't limit ORM functionalities. – Piotr Perak Apr 08 '13 at 10:57
  • @Peri Any proper repository. The application using the repo doesn't need to know about the ORM. THe ORM is a persistence layer detail and it should stay there. Let the ORM provide an abstraction over a RDBMS while the repository shields the app from anyting related to persistence. If the rest of the app knows about the ORM or worse, it depends on it, you need to check again what Separation of Concerns means. – MikeSW Apr 08 '13 at 12:35
  • @MikeSW: I ask again. Show me that implementation you talk about. If you can't I assume you haven't seen/created one but talk about something you imagine is possible. – Piotr Perak Apr 08 '13 at 20:43
  • @Peri Nice try. Here, read my post about this matter http://www.sapiensworks.com/blog/post/2012/04/15/The-Repository-Pattern-Vs-ORM.aspx – MikeSW Apr 09 '13 at 07:29
  • Still no code :) Maybe you should read something about repositories here: http://ayende.com/blog/3955/repository-is-the-new-singleton. Or some answers by Ladislav Mrnka on SO. – Piotr Perak Apr 09 '13 at 16:58