9

Meine Anwendung verwendet Entity Framework 7 und das Repository-Muster.Repository-Muster verwenden, um Entitäten mithilfe von ThenIclude zu laden.

Die GetById Methode auf dem Repository unterstützt eifrigen Laden von Unterinformationseinheiten:

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); 
    } 

Verwendung ist wie folgt mit diesem Produkt verbundenen ein Produkt (dessen ID-2) zusammen mit den Aufträgen und den Teilen zu erhalten:

Ich möchte diese Methode verbessern, um das eifrige Laden von Entitäten zu unterstützen, die tiefer als eine Ebene verschachtelt sind. Angenommen, ein Order hat seine eigene Sammlung von LineItem.

Vor EF7 Ich glaube, die folgende möglich gewesen wäre, auch die Lineitem bei jeder Bestellung zugeordnet abrufen:

productRepository.GetById(2, p => p.Orders.Select(o => o.LineItems), p => p.Parts); 

aber dies scheint nicht in EF7 unterstützt werden. Stattdessen gibt es eine neue ThenInclude Methode, die zusätzliche Ebene von verschachtelten Einheiten abruft:

https://github.com/aspnet/EntityFramework/wiki/Design-Meeting-Notes:-January-8,-2015

ich nicht sicher bin, wie mein Repository zu aktualisieren, den Abruf von mehreren Ebenen eifrig geladen Einheiten ThenInclude mit zu unterstützen.

Antwort

5

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.

+0

Gibt es einen Leistungsnachteil für Joining zuerst und dann Auswahl (über FirstOrDefault() im Vergleich zur Auswahl zuerst? – phhbr

+1

@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

6

Sie können es so etwas ändern:

public virtual TEntity GetById<TEntity>(int id, Func<IQueryable<TEntity>, IQueryable<TEntity>> func) 
{ 
    DbSet<TEntity> result = this.Set<TEntity>(); 

    IQueryable<TEntity> resultWithEagerLoading = func(result); 

    return resultWithEagerLoading.FirstOrDefault(e => e.Id == id); 
} 


Und Sie können es wie folgt verwenden:

productRepository.GetById(2, x => x.Include(p => p.Orders) 
            .ThenInclude(o => o.LineItems) 
            .Include(p => p.Parts)) 
+0

Danke, dass Sie sich die Zeit genommen haben, zu antworten. Das Problem, das ich mit Ihrer vorgeschlagenen Lösung habe, ist, dass es IQueryable/Entity Framework-Aufrufe außerhalb des Repository verfügbar macht. Ich ziehe es vor, dies nicht zu tun. – aw1975

+2

Ich verstehe Ihre Bedenken. Eine Möglichkeit, diese Persistenz-Ignoranz zu erreichen, besteht darin, einige Objekte in Ihrer Datenschicht zu erstellen, um Ihre Abrufdetails zu kapseln (EF-Includes) und dann einige Schnittstellen implementieren zu lassen. Dann können Sie diese Schnittstelle an Ihre Repository-Methode übergeben und über das Repository das implementierte Objekt auflösen und dann die Includes innerhalb dieses Objekts aufrufen. Es ist ein bisschen Arbeit, das einzurichten, aber so kann ich das eifrige Laden in meinen Projekten implementieren. –