Eager loading and repository pattern

Viewed 7066

I'm wondering how to properly handle eager-loading problem for complex object graphs when using Repository pattern. This isn't ORM specific problem i guess.

First try:

public interface IProductRepository : IRepository<Product>
{
  Product GetById(int id);
  IProductRepository WithCustomers();
}

This would work fine, but that would involve repeating myself all the time (writing custom 'With' methods in repository implementations everywhere).

Next approach:

public interface IRepository<T> where T : IAggregateRoot
{
  ...
  void With(Expression<Func<T, object>> propToExpand);
}

With method will add an item to private collection which will be used later to find out what props should be eager loaded when retrieving necessary entity/ies.

This kind a works and is fine. But i dislike usage:

productRepository.With(x=>x.Customer);
productRepository.With(x=>x.Price);
productRepository.With(x=>x.Manufacturer);
var product = productRepository.GetById(id);

Basically - problem is that there isn't chaining. I would like it to be like this:

var product = productRepository
  .With(x=>x.Customer)
  .With(x=>x.Price)
  .With(x=>x.Manufacturer)
  .GetById(id);

I couldn't achieve this. Even if i could - i'm not sure if that solution would be elegant.

This leads to thoughts that i'm missing something fundamental (lack of examples anywhere). Are there different ways how to handle this? What are best practices?

7 Answers

In the BaseRepository.cs you can create this method:

public async Task<IEnumerable<T>> GetWithChild(string child)
{
    return await _entities.Include(child).ToListAsync();
}

In my API I have also implemented a service layer but from the API I simply call this method and pass it the name of the variable to load.

Obviously, in your situation, you'll need to include a few more strings.

I posted an answer earlier but I still wasn't happy with the solution. So here is a better solution.

in the BaseRepository.cs

public async Task<IEnumerable<T>> GetAll(params Expression<Func<T, object>>[] properties)
{
      IQueryable<T> query = _entities;

      query = properties.Aggregate(query, (current, property) => current.Include(property));

      return await query.AsNoTracking().ToListAsync();
}

and you can simply use the method as follows

await _service.GetAll(x => x.Customer, x => x.Price, x => x.Manufacturer); 
Related