SOfanatic
SOfanatic

Reputation: 5573

translate this into a generic repository pattern

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

Upvotes: 2

Views: 950

Answers (1)

Slauma
Slauma

Reputation: 177163

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.

Upvotes: 5

Related Questions