web-dev-qa-db-fra.com

Chargement enthousiaste d'Entity Framework Core 2.0.1 sur toutes les entités liées imbriquées

J'ai un problème simple, mais je n'arrive pas à trouver un moyen de le contourner. J'utilise Entity Framework Core version 2.0.1 et je souhaite charger toutes mes entités par défaut.

Exemple:

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public int Id { get; set; } 
    public string Name { get; set; }
    public int AddressId { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string PostCode { get; set; }
    public string City { get; set; }
}

Mais quand je charge l'entité Order l'entité associée Customer puis à l'intérieur Address est null

Ce que j'ai essayé:

  • Tentative de mise à niveau vers la version 2.1 et utilisation de LazyLoadingProxies sur false

Ceci est juste un exemple, j'ai des entités avec plusieurs niveaux imbriqués et je veux charger des données liées imbriquées à l'intérieur d'un référentiel générique, donc je ne peux pas utiliser Include et ThenInclude as Je ne connais pas le type d'entité réel lors du chargement.

Exemple:

    public virtual async Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null)
    {
        if (predicate == null)
        {
            return await Context.Set<T>().ToListAsync();
        }
        return await Context.Set<T>().Where(predicate).ToListAsync();
    }

Qu'est-ce que je rate? Y a-t-il quelque chose qui ne va pas dans le référentiel? Toute aide ou pointeur vers une meilleure conception (si c'est le problème ici) est apprécié.

Merci

11
Jinish

Cette fonctionnalité n'existe officiellement pas actuellement (EF Core 2.0.2 et aussi le 2.1 entrant). Il a été demandé dans Désireux de charger toutes les propriétés de navigation # 4851 (Fermé) et est actuellement suivi par Charge avide basée sur des règles (inclure) # 295 et Autoriser pour déclaration d'agrégats dans le modèle (par exemple, définition des propriétés incluses ou par d'autres moyens) # 1985 (les deux dans Backlog, c'est-à-dire sans calendrier concret).

Je peux proposer les deux méthodes d'extension personnalisées suivantes:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomExtensions
    {
        public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> navigationPropertyPaths)
            where T : class
        {
            return navigationPropertyPaths.Aggregate(source, (query, path) => query.Include(path));
        }

        public static IEnumerable<string> GetIncludePaths(this DbContext context, Type clrEntityType)
        {
            var entityType = context.Model.FindEntityType(clrEntityType);
            var includedNavigations = new HashSet<INavigation>();
            var stack = new Stack<IEnumerator<INavigation>>();
            while (true)
            {
                var entityNavigations = new List<INavigation>();
                foreach (var navigation in entityType.GetNavigations())
                {
                    if (includedNavigations.Add(navigation))
                        entityNavigations.Add(navigation);
                }
                if (entityNavigations.Count == 0)
                {
                    if (stack.Count > 0)
                        yield return string.Join(".", stack.Reverse().Select(e => e.Current.Name));
                }
                else
                {
                    foreach (var navigation in entityNavigations)
                    {
                        var inverseNavigation = navigation.FindInverse();
                        if (inverseNavigation != null)
                            includedNavigations.Add(inverseNavigation);
                    }
                    stack.Push(entityNavigations.GetEnumerator());
                }
                while (stack.Count > 0 && !stack.Peek().MoveNext())
                    stack.Pop();
                if (stack.Count == 0) break;
                entityType = stack.Peek().Current.GetTargetType();
            }
        }

    }
}

Le premier est juste un moyen pratique d'appliquer plusieurs chaînes de base Include.

Le second fait le travail réel de collecte de tous les chemins Include pour un type à l'aide des métadonnées fournies par EF Core. Il s'agit essentiellement d'un traitement de graphe cyclique dirigé commençant par le type d'entité transmis, excluant les navigations inverses des chemins inclus et n'émettant que les chemins vers les nœuds "feuilles".

L'utilisation dans votre exemple pourrait ressembler à ceci:

public virtual async Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null)
{
    var query = Context.Set<T>()
        .Include(Context.GetIncludePaths(typeof(T));
    if (predicate != null)
        query = query.Where(predicate);
    return await query.ToListAsync();
}
23
Ivan Stoev