My application uses Entity Framework 7 and the repository pattern.
The GetById method on the repository supports eager loading of child entities:
public virtual TEntity GetById(int id, params Expression<Func<TEntity, object>>[] paths)
{
var result = this.Set.Include(paths.First());
foreach (var path in paths.Skip(1))
{
result = result.Include(path);
}
return result.FirstOrDefault(e => e.Id == id);
}
Usage is as follows to retrieve a product (whose id is 2) along with the orders and the parts associated with that product:
productRepository.GetById(2, p => p.Orders, p => p.Parts);
I want to enhance this method to support eager loading of entities nested deeper than one level. For example suppose an Order
has its own collection of LineItem
's.
Prior to EF7 I believe the following would have been possible to also retrieve the LineItems associated with each order:
productRepository.GetById(2, p => p.Orders.Select(o => o.LineItems), p => p.Parts);
However this doesn't appear to be supported in EF7. Instead there is a new ThenInclude method that retrieves additional levels of nested entities:
https://github.com/aspnet/EntityFramework/wiki/Design-Meeting-Notes:-January-8,-2015
I am unsure as to how to update my repository to support retrieval of multiple-levels of eager loaded entities using ThenInclude
.
EF Core already implements a Rep/UoW pattern, so layering another Rep/UoW pattern on top of EF Core isn't helpful. A better solution is to use EF Core directly, which allows you to use all of EF Core's feature to produce high-performing database accesses.
Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query, so that we don't need to execute a separate query for related entities. Eager loading is achieved using the Include() method.
Repository Pattern is an abstraction of the Data Access Layer. It hides the details of how exactly the data is saved or retrieved from the underlying data source. The details of how the data is stored and retrieved is in the respective repository.
Entity Framework Classic ThenIncludeThe ThenInclude method moves the chaining level to the property included. It allows us to include related objects from the next level. ThenInclude is a syntactic sugar method to make it easier and clearer to include multiple related objects.
This is a bit of an old question, but since it doesn't have an accepted answer I thought I'd post my solution to this.
I'm using EF Core and wanted to do exactly this, access eager loading from outside my repository class so I can specify the navigation properties to load each time I call a repository method. Since I have a large number of tables and data I didn't want a standard set of eagerly loading entities since some of my queries only needed the parent entity and some needed the whole tree.
My current implementation only supports IQueryable
method (ie. FirstOrDefault
, Where
, basically the standard lambda functions) but I'm sure you could use it to pass through to your specific repository methods.
I started with the source code for EF Core's EntityFrameworkQueryableExtensions.cs
which is where the Include
and ThenInclude
extension methods are defined. Unfortunately, EF uses an internal class IncludableQueryable
to hold the tree of previous properties to allow for strongly type later includes. However, the implementation for this is nothing more than IQueryable
with an extra generic type for the previous entity.
I created my own version I called IncludableJoin
that takes an IIncludableQueryable
as a constructor parameter and stores it in a private field for later access:
public interface IIncludableJoin<out TEntity, out TProperty> : IQueryable<TEntity>
{
}
public class IncludableJoin<TEntity, TPreviousProperty> : IIncludableJoin<TEntity, TPreviousProperty>
{
private readonly IIncludableQueryable<TEntity, TPreviousProperty> _query;
public IncludableJoin(IIncludableQueryable<TEntity, TPreviousProperty> query)
{
_query = query;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<TEntity> GetEnumerator()
{
return _query.GetEnumerator();
}
public Expression Expression => _query.Expression;
public Type ElementType => _query.ElementType;
public IQueryProvider Provider => _query.Provider;
internal IIncludableQueryable<TEntity, TPreviousProperty> GetQuery()
{
return _query;
}
}
Note the internal GetQuery
method. This will be important later.
Next, in my generic IRepository
interface, I defined the starting point for eager loading:
public interface IRepository<TEntity> where TEntity : class
{
IIncludableJoin<TEntity, TProperty> Join<TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty);
...
}
The TEntity
generic type is the interface of my EF entity. The implmentation of the Join
method in my generic repository is like so:
public abstract class SecureRepository<TInterface, TEntity> : IRepository<TInterface>
where TEntity : class, new()
where TInterface : class
{
protected DbSet<TEntity> DbSet;
protected SecureRepository(DataContext dataContext)
{
DbSet = dataContext.Set<TEntity>();
}
public virtual IIncludableJoin<TInterface, TProperty> Join<TProperty>(Expression<Func<TInterface, TProperty>> navigationProperty)
{
return ((IQueryable<TInterface>)DbSet).Join(navigationProperty);
}
...
}
Now for the part that actually allows for multiple Include
and ThenInclude
. I have several extension methods that take and return and IIncludableJoin
to allow for method chaining. Inside which I call the EF Include
and ThenInclude
methods on the DbSet:
public static class RepositoryExtensions
{
public static IIncludableJoin<TEntity, TProperty> Join<TEntity, TProperty>(
this IQueryable<TEntity> query,
Expression<Func<TEntity, TProperty>> propToExpand)
where TEntity : class
{
return new IncludableJoin<TEntity, TProperty>(query.Include(propToExpand));
}
public static IIncludableJoin<TEntity, TProperty> ThenJoin<TEntity, TPreviousProperty, TProperty>(
this IIncludableJoin<TEntity, TPreviousProperty> query,
Expression<Func<TPreviousProperty, TProperty>> propToExpand)
where TEntity : class
{
IIncludableQueryable<TEntity, TPreviousProperty> queryable = ((IncludableJoin<TEntity, TPreviousProperty>)query).GetQuery();
return new IncludableJoin<TEntity, TProperty>(queryable.ThenInclude(propToExpand));
}
public static IIncludableJoin<TEntity, TProperty> ThenJoin<TEntity, TPreviousProperty, TProperty>(
this IIncludableJoin<TEntity, IEnumerable<TPreviousProperty>> query,
Expression<Func<TPreviousProperty, TProperty>> propToExpand)
where TEntity : class
{
var queryable = ((IncludableJoin<TEntity, IEnumerable<TPreviousProperty>>)query).GetQuery();
var include = queryable.ThenInclude(propToExpand);
return new IncludableJoin<TEntity, TProperty>(include);
}
}
In these methods I am getting the internal IIncludableQueryable
property using the aforementioned GetQuery
method, calling the relevant Include
or ThenInclude
method, then returning a new IncludableJoin
object to support the method chaining.
And that's it. The usage of this is like so:
IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);
The above would load the base Account
entity, it's one-to-one child Subscription
, it's one-to-many child list Addresses
and it's child Address
. Each lambda function along the way is strongly typed and is supported by intellisense to show the properties available on each entity.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With