Mit Hilfe von repository-pattern zu eifrig laden Entitäten mit ThenIclude

Meine Anwendung verwendet Entity Framework 7 und das repository-pattern.

Die GetById-Methode für den repository unterstützt eager loading von Kind-Entitäten:

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

Nutzung ist wie folgt abrufen eines Produktes (mit der id 2) zusammen mit den Ordnungen und der Teile, die in Verbindung mit diesem Produkt:

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

Möchte ich auf eine Verbesserung dieser Methode zu unterstützen, eager loading von Entitäten, die verschachtelte tiefer als eine Ebene. Zum Beispiel nehmen wir an, eine Order hat seine eigene Sammlung von LineItem’s.

Vor EF7 ich glaube, die folgenden möglich gewesen wäre, abrufen, die der LineItems in Verbindung mit jeder einzelnen Bestellung:

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

Aber dies scheint nicht unterstützt werden, EF7. Stattdessen gibt es eine neue ThenInclude Methode, ruft zusätzliche Ebenen von verschachtelten entities:

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

Ich bin mir nicht sicher, wie aktualisiere ich meine repository unterstützt den Abruf von mehreren Ebenen von eager geladen Entitäten mit ThenInclude.

InformationsquelleAutor aw1975 | 2016-05-26

 

2 Replies
  1. 8

    Dies ist ein bisschen eine alte Frage, aber da es nicht über eine akzeptierte Antwort, die ich dachte, ich würde post meine Lösung für dieses.

    Ich bin mit EF-Core und wollte genau das tun, Zugang eager loading außerhalb meiner repository-Klasse, so kann ich geben Sie die Navigations-Eigenschaften zum laden jedes mal, wenn ich rufe eine repository-Methode. Da habe ich eine große Anzahl von Tabellen und Daten, die ich nicht wollen, ein standard-set von eifrig be-Einheiten, da einige meiner Abfragen nur nötig, die übergeordnete Entität und einige brauchten den ganzen Baum.

    Meine aktuelle Implementierung unterstützt nur IQueryable – Methode (ie. FirstOrDefault , Where im Grunde sind die standard lambda-Funktionen), aber ich bin sicher, Sie könnten es verwenden, um zu Durchlaufen, um Ihre spezifischen repository-Methoden.

    Begann ich mit dem source code für EF-Core EntityFrameworkQueryableExtensions.cs, die ist, wo die Include und ThenInclude Erweiterung Methoden definiert sind. Leider, EF verwendet eine interne Klasse IncludableQueryable um den Baum zu halten, von früheren Eigenschaften zu ermöglichen stark Typ später schließt. Allerdings ist die Umsetzung für diese ist nichts anderes als IQueryable mit einem zusätzlichen generischen Typ der für die Vorherige Einheit.

    Ich meine eigene version, die ich genannt IncludableJoin braucht man ein IIncludableQueryable als Konstruktor-parameter und speichert diese in einem privaten Bereich für den späteren Zugriff:

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

    Hinweis: die interne GetQuery Methode. Dies wird später wichtig.

    Nächsten, in meinem generic IRepository Schnittstelle definiert, die Ausgangspunkt für eager loading:

    public interface IRepository<TEntity> where TEntity : class
    {
        IIncludableJoin<TEntity, TProperty> Join<TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty);
        ...
    }

    Den TEntity generische Typ der Schnittstelle meiner EF entity. Die-Implementierung der Join Methode in meinem generic repository ist 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);
        }
        ...
    }

    Nun für den Teil, der tatsächlich ermöglicht mehrere Include und ThenInclude. Ich habe verschiedene Verlängerung Methoden, nehmen und zurückgeben und IIncludableJoin zu ermöglichen, für die Verkettung von Methoden. Innerhalb ich nenne das EF Include und ThenInclude Methoden auf die 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 bin ich immer die interne IIncludableQueryable Eigenschaft, die über die vorgenannten GetQuery – Methode, aufrufen der entsprechenden Include oder ThenInclude Methode, dann wieder eine neue IncludableJoin Objekt unterstützt die Methode der Verkettung.

    … Und das ist es. Die Nutzung dieser lautet in etwa so:

    IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);

    Den oben genannten laden würde die Basis Account Entität, es ist eins-zu-eins-Kind Subscription, es ist eins-zu-viele child-Liste Addresses und Kind Address. Jede lambda-Funktion entlang des Weges ist stark typisiert und von intellisense unterstützt wird und die Eigenschaften zur Verfügung, auf jeder Einheit.

    • Wird es ein performance-Nachteil für das verbinden von ersten und dann auswählen (über FirstOrDefault() im Vergleich zu der Auswahl ersten?
    • Ich glaube nicht, dass es so ist? Ich glaube nicht, dass es wichtig ist, weil wenn Sie sitzen in einem IQueryable die Abfrage nicht ausgeführt, bis Sie den Anruf .ToList oder ähnliches. So wird die Reihenfolge der Vorgänge wahrscheinlich egal, aber ich habe es noch nicht getestet.
  2. 7

    Können Sie es ändern, um so etwas wie dieses:

    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 verwenden, wie diese:

    productRepository.GetById(2, x => x.Include(p => p.Orders)
                                       .ThenInclude(o => o.LineItems)
                                       .Include(p => p.Parts))
    • Vielen Dank für die Zeit nehmen, zu reagieren. Das Problem habe ich mit deiner vorgeschlagenen Lösung ist, dass es macht IQueryable/Entity Framework, Anrufe außerhalb des Repositorys. Ich bevorzuge dies nicht zu tun.
    • Ich verstehe Ihre Bedenken. Eine Möglichkeit, um dieses „persistence ignorance“ ist, um einige Objekte in der data-layer zu Kapseln Holen Ihre Daten (EF Enthält) und dann haben Sie Sie implementieren Schnittstellen. Dann kann passieren, dass die Schnittstelle zu Ihrer repository-Methode und dem repository beheben das implementierte Objekt und rufen Sie dann die includes in das Objekt. Es ist ein bisschen Arbeit, um das zu setzen, aber das ist, wie ich das umsetzen eager loading in meinen Projekten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.