Reputation: 5573
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
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