Das ist ein bisschen eine alte Frage, aber da es doesn Ich habe keine akzeptierte Antwort. Ich dachte, ich würde meine Lösung dafür veröffentlichen.
Ich benutze EF Core und wollte genau dies tun, Zugriff auf eifrigen Laden von außerhalb meiner Repository-Klasse, so dass ich die Navigationseigenschaften angeben kann, um jedes Mal, wenn ich eine Repository-Methode aufrufen. Da ich eine große Anzahl von Tabellen und Daten habe, wollte ich keinen Standardsatz von eifrig ladenden Entitäten, da einige meiner Abfragen nur die übergeordnete Entität benötigten und einige die gesamte Struktur benötigten.
Meine aktuelle Implementierung unterstützt nur IQueryable
Methode (dh. FirstOrDefault
, Where
, im Grunde der Standard-Lambda-Funktionen), aber ich bin sicher, dass Sie es verwenden könnten durch Ihre spezifische Repository Methoden zu übergeben.
Ich begann mit dem Quellcode für EF Core EntityFrameworkQueryableExtensions.cs
, wo die Include
und ThenInclude
Erweiterung Methoden definiert sind. Unglücklicherweise verwendet EF eine interne Klasse, um den Baum früherer Eigenschaften zu halten, um später eingeschlossene Typen zuzulassen. Die Implementierung dafür ist jedoch nicht mehr als IQueryable
mit einem zusätzlichen generischen Typ für die vorherige Entität.
ich meine eigene Version, die ich IncludableJoin
genannt, die für einen späteren Zugriff einen IIncludableQueryable
als Konstrukteur Parameter und speichert sie in einem privaten Bereich nimmt:
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;
}
}
Notiere die interne GetQuery
Methode. Dies wird später wichtig sein.
nächste in meinem generic IRepository
Schnittstelle, definierte ich den Ausgangspunkt für eager loading:
public interface IRepository<TEntity> where TEntity : class
{
IIncludableJoin<TEntity, TProperty> Join<TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty);
...
}
Der TEntity
generischen Typ ist die Schnittstelle meine EF Einheit. Die implmentation der Join
Methode in meiner generic-Repository ist wie 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);
}
...
}
Jetzt für den Teil, der ermöglicht Include
mehr tatsächlich und ThenInclude
. Ich habe mehrere Erweiterungsmethoden, die übernehmen und zurückgeben und IIncludableJoin
für die Methode Verkettung zulassen. In dessen Innerem nenne ich das EF Include
und ThenInclude
Methoden auf dem 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 diesen Methoden, die ich die interne IIncludableQueryable
Eigenschaft bin immer die oben genannte GetQuery
Methode verwenden, die entsprechende Include
oder ThenInclude
Methode aufrufen, dann ein neues IncludableJoin
Rückkehr Objekt zur Unterstützung der Methodenverkettung.
Und das ist es. Die Verwendung dieses wie so ist:
IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);
die über die Basis Account
Einheit laden würde, es ist eine Eins-zu-Eins-Kind Subscription
, dann ist es eine Eins-zu-viele Child-Liste Addresses
und es ist Kind Address
. Jede Lambda-Funktion auf dem Weg ist stark typisiert und wird von Intellisense unterstützt, um die Eigenschaften anzuzeigen, die für jede Entität verfügbar sind.
Gibt es einen Leistungsnachteil für Joining zuerst und dann Auswahl (über FirstOrDefault() im Vergleich zur Auswahl zuerst? – phhbr
@phhbr ich glaube nicht? Ich glaube nicht, dass es wichtig ist, denn wenn Sie in einem 'IQueryable' sitzen Abfrage wird nicht ausgeführt, bis Sie '.ToList' oder ähnliches aufrufen. Also die Reihenfolge der Vorgänge ist wahrscheinlich egal, aber ich habe es nicht getestet. – Steve