86 votes

Entity Framework - Existe-t-il un moyen de charger automatiquement les entités enfants sans utiliser Include()?

Existe-t-il un moyen de décorer vos classes POCO pour charger automatiquement les entités enfants sans avoir à utiliser Include() à chaque fois que vous les chargez ?

Disons que j'ai une classe Car, avec des propriétés de type complexe pour les roues, les portes, le moteur, le pare-chocs, les fenêtres, l'échappement, etc. Et dans mon application, je dois charger ma voiture depuis mon DbContext à 20 endroits différents avec différentes requêtes, etc. Je ne veux pas avoir à spécifier que je veux inclure toutes les propriétés à chaque fois que je veux charger ma voiture.

Je veux dire

List cars = db.Car
                .Where(x => c.Make == "Ford").ToList();

//PAS .Include(x => x.Wheels).Include(x => x.Doors).Include(x => x.Engine).Include(x => x.Bumper).Include(x => x.Windows)

foreach(Car car in cars)
{

//Je ne veux pas de référence nulle ici.

String myString = car.**Bumper**.Titre;
}

Puis-je d'une manière ou d'une autre décorer ma classe POCO ou dans mon OnModelCreating() ou définir une configuration dans EF qui lui indiquera de simplement charger toutes les parties de ma voiture lorsque je charge ma voiture ? Je veux faire cela en mode eager, donc à mon avis, rendre mes propriétés de navigation virtuelles est exclu. Je sais que NHibernate prend en charge une fonctionnalité similaire.

Je me demande juste si j'ai raté quelque chose. Merci d'avance !

Amicalement,

Nathan

J'aime la solution ci-dessous, mais je me demande si je peux imbriquer les appels aux méthodes d'extension. Par exemple, disons que j'ai une situation similaire avec Engine où il a de nombreuses pièces que je ne veux pas inclure partout. Puis-je faire quelque chose comme ça ? (Je n'ai pas encore trouvé de moyen pour que cela fonctionne). De cette manière, si plus tard je découvre que Engine a besoin d'Injecteurs de carburant, je peux l'ajouter uniquement dans BuildEngine et ne pas avoir à l'ajouter également dans BuildCar. Aussi, si je peux imbriquer les appels, comment puis-je imbriquer un appel à une collection? Comme appeler BuildWheel() pour chacune de mes roues depuis mon BuildCar()?

public static IQueryable BuildCar(this IQueryable query) {
     return query.Include(x => x.Wheels).BuildWheel()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine).BuildEngine()
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

public static IQueryable BuildEngine(this IQueryable query) {
     return query.Include(x => x.Pistons)
                 .Include(x => x.Cylinders);
}

//Ou pour gérer une collection par exemple.
public static IQueryable BuildWheel(this IQueryable query) {
     return query.Include(x => x.Rim)
                 .Include(x => x.Tire);
}

Voici un autre fil de discussion très similaire au cas où cela serait utile à quelqu'un d'autre dans cette situation, mais cela ne gère toujours pas la possibilité de faire des appels imbriqués aux méthodes d'extension.

Entity framework linq query Include() multiple children entities

70voto

Ladislav Mrnka Points 218632

Non, vous ne pouvez pas le faire dans le mapping. La solution de contournement typique est une simple méthode d'extension :

public static IQueryable BuildCar(this IQueryable query) {
     return query.Include(x => x.Wheels)
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

Maintenant, chaque fois que vous voulez interroger une Car avec toutes ses relations, vous ferez simplement :

var query = from car in db.Cars.BuildCar()
            where car.Make == "Ford"
            select car;

Éditer :

Vous ne pouvez pas imbriquer les appels de cette façon. Include fonctionne sur l'entité de base avec laquelle vous travaillez - cette entité définit la forme de la requête, donc après avoir appelé Include(x => Wheels), vous travaillez toujours avec IQueryable et vous ne pouvez pas appeler la méthode d'extension pour IQueryable. Vous devez recommencer avec Car:

public static IQueryable BuildCarWheels(this IQuerable query) {
    // Cela répond également à la manière de charger en mémoire de manière anticipée des collections imbriquées
    // D'ailleurs, seul le Select est autorisé - vous ne pouvez pas utiliser Where, OrderBy ou autre chose
    return query.Include(x => x.Wheels.Select(y => y.Rim))
                .Include(x => x.Wheels.Select(y => y.Tire));
}

et vous utiliserez cette méthode de cette façon :

public static IQueryable BuildCar(this IQueryable query) {
     return query.BuildCarWheels()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

L'utilisation ne mentionne pas Include(x => x.Wheels) car cela devrait être ajouté automatiquement lors de la demande de chargement anticipé de ses entités imbriquées.

Méfiez-vous des requêtes complexes produites par de telles structures de chargement anticipé complexes. Cela peut entraîner de très mauvaises performances et beaucoup de données dupliquées transférées depuis la base de données.

19voto

Ado Points 324

Depuis EF Core 6, il existe une méthode AutoInclude qui configure si une navigation doit être incluse automatiquement.

Cela peut être fait dans la méthode OnModelCreating de la classe DbContext :

modelBuilder.Entity().Navigation(e => e.Wheels).AutoInclude();

Cela chargerait les Roues pour chaque Voiture lors de l'exécution de la requête suivante.

var cars = dbContext.Cars.ToList();

Voir Configuration du modèle pour inclure automatiquement les navigations

9voto

Lunyx Points 1605

Avait le même problème et a vu l'autre lien qui mentionnait un Include attribut. Ma solution suppose que vous avez créé un attribut appelé IncludeAttribute. Avec la méthode d'extension suivante et la méthode utilitaire:

    public static IQueryable LoadRelated(this IQueryable originalQuery)
    {
        Func, IQueryable> includeFunc = f => f;
        foreach (var prop in typeof(T).GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof(IncludeAttribute))))
        {
            Func, IQueryable> chainedIncludeFunc = f => f.Include(prop.Name);
            includeFunc = Compose(includeFunc, chainedIncludeFunc);
        }
        return includeFunc(originalQuery);
    }

    private static Func Compose(Func innerFunc, Func outerFunc)
    {
        return arg => outerFunc(innerFunc(arg));
    }

1voto

EyeSirvived Points 131

Travaillant davantage sur la réponse de Lunyx, j'ai ajouté une surcharge pour le faire fonctionner avec une collection de chaînes (où une chaîne est un nom de propriété comme "propriétéA", ou "propriétéA. propriétéDeAA" par exemple) :

  public static IQueryable LoadRelated(this IQueryable originalQuery, ICollection includes)
    {
        if (includes == null || !includes.Any()) return originalQuery;

        Func, IQueryable> includeFunc = f => f;
        foreach (var include in includes)
        {
            IQueryable ChainedFunc(IQueryable f) => f.Include(include);
            includeFunc = Compose(includeFunc, ChainedFunc);
        }

        return includeFunc(originalQuery);
    }

0voto

bubi Points 29

Si vous avez VRAIMENT besoin d'inclure toutes les propriétés de navigation (vous pourriez avoir des problèmes de performance) vous pouvez utiliser ce code.
Si vous devez également charger de manière anticipée des collections (même une pire idée) vous pouvez supprimer l'instruction continue dans le code.
Le contexte est votre contexte (si vous insérez ce code dans votre contexte)

    public IQueryable IncludeAllNavigationProperties(IQueryable queryable)
    {
        if (queryable == null)
            throw new ArgumentNullException("queryable");

        ObjectContext objectContext = ((IObjectContextAdapter)Context).ObjectContext;
        var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace();

        EntitySetMapping[] entitySetMappingCollection = metadataWorkspace.GetItems(DataSpace.CSSpace).Single().EntitySetMappings.ToArray();

        var entitySetMappings = entitySetMappingCollection.First(o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(typeof(T).Name));

        var entityTypeMapping = entitySetMappings.EntityTypeMappings[0];

        foreach (var navigationProperty in entityTypeMapping.EntityType.NavigationProperties)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(navigationProperty.Name);
            if (propertyInfo == null)
                throw new InvalidOperationException("propertyInfo == null");
            if (typeof(System.Collections.IEnumerable).IsAssignableFrom(propertyInfo.PropertyType))
                continue;

            queryable = queryable.Include(navigationProperty.Name);
        }

        return queryable;
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X