Questo è un po 'di una vecchia questione, ma dal momento che doesn Ho una risposta accettata Ho pensato di pubblicare la mia soluzione a questo.
Sto usando EF Core e volevo fare esattamente questo, accedendo con entusiasmo il caricamento dall'esterno della mia classe di repository in modo da poter specificare le proprietà di navigazione da caricare ogni volta che chiamo un metodo di repository. Dato che ho un gran numero di tabelle e dati, non volevo un set standard di entità caricate avidamente poiché alcune delle mie query richiedevano solo l'entità padre e alcune avevano bisogno dell'intero albero.
mio attuale implementazione supporta solo IQueryable
metodo (es. FirstOrDefault
, Where
, fondamentalmente le funzioni lambda normali), ma sono sicuro che si potrebbe utilizzare per passare attraverso i vostri metodi di repository specifici.
Ho iniziato con il codice sorgente per EF Core EntityFrameworkQueryableExtensions.cs
che è dove sono definiti i metodi di estensione Include
e ThenInclude
. Sfortunatamente, EF utilizza una classe interna IncludableQueryable
per contenere l'albero delle proprietà precedenti per consentire di digitare in modo consistente i successivi include. Tuttavia, l'implementazione per questo non è altro che IQueryable
con un tipo generico extra per l'entità precedente.
ho creato una mia versione ho chiamato IncludableJoin
che prende un IIncludableQueryable
come parametro del costruttore e lo memorizza in un campo privato di accesso più tardi:
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;
}
}
Annotare il GetQuery
metodo interno. Questo sarà importante più tardi.
successiva, nel mio interfaccia generica IRepository
, ho definito il punto di partenza per il caricamento ansiosi:
public interface IRepository<TEntity> where TEntity : class
{
IIncludableJoin<TEntity, TProperty> Join<TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty);
...
}
Il tipo generico TEntity
è l'interfaccia della mia entità EF. L'implmentation del metodo Join
nel mio repository generico è in questo modo:
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);
}
...
}
Ora, per la parte che consente in realtà per più Include
e ThenInclude
. Ho diversi metodi di estensione che prendono e restituiscono e IIncludableJoin
per consentire il concatenamento del metodo. All'interno che io chiamo l'EF Include
e ThenInclude
metodi sulla 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 questi metodi che sto ottenendo il IIncludableQueryable
proprietà interna usando il suddetto metodo di GetQuery
, chiamando il relativo metodo di Include
o ThenInclude
, per poi tornare un nuovo IncludableJoin
oggetto per supportare il concatenamento del metodo.
E questo è tutto. L'utilizzo di questo è in questo modo:
IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);
È possibile che questo sarebbe caricare l'entità di base Account
, è uno-a-un bambino Subscription
, è uno-a-molti list bambino Addresses
ed è figlio Address
. Ogni funzione lambda lungo il percorso è fortemente tipizzata ed è supportata da intellisense per mostrare le proprietà disponibili su ciascuna entità.
Ci sarà uno svantaggio delle prestazioni per Joining prima e quindi selezionare (tramite FirstOrDefault() rispetto alla selezione prima? – phhbr
@phhbr Non penso che sia così? Non penso che importi perché quando siedi in un 'IQueryable' il la query non viene eseguita fino a quando non si chiama '.ToList' o simile. Quindi l'ordine delle operazioni probabilmente non ha importanza, ma non l'ho testato. – Steve