Existe-t-il un moyen de décorer vos classes POCO pour charger automatiquement des entités enfants sans avoir à utiliser Include()
chaque fois que vous les chargez?
Disons que j'ai une voiture de classe, avec des propriétés de type complexe pour les roues, les portes, le moteur, le pare-chocs, les fenêtres, les gaz d'échappement, etc. Et dans mon application, je dois charger ma voiture depuis mon DbContext 20 endroits différents avec des requêtes différentes, 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<Car> cars = db.Car
.Where(x => c.Make == "Ford").ToList();
//NOT .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)
{
//I don't want a null reference here.
String myString = car.**Bumper**.Title;
}
Puis-je en quelque sorte décorer ma classe POCO ou dans ma OnModelCreating()
ou définir une configuration dans EF qui lui dira de charger toutes les pièces de ma voiture lorsque je charge ma voiture? Je veux le faire avec impatience, donc je comprends que la virtualisation de mes propriétés de navigation est terminée. Je sais que NHibernate prend en charge des fonctionnalités similaires.
Je me demande juste si je manque quelque chose. Merci d'avance!
À votre santé,
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 parties 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 façon, si plus tard je découvre que le moteur a besoin d'injecteurs de carburant, je peux l'ajouter uniquement dans le 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? Vous souhaitez appeler BuildWheel () pour chacune de mes roues depuis ma BuildCar ()?
public static IQueryable<Car> BuildCar(this IQueryable<Car> 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<Engine> BuildEngine(this IQueryable<Engine> query) {
return query.Include(x => x.Pistons)
.Include(x => x.Cylendars);
}
//Or to handle a collection e.g.
public static IQueryable<Wheel> BuildWheel(this IQueryable<Wheel> query) {
return query.Include(x => x.Rim)
.Include(x => x.Tire);
}
Voici un autre thread très similaire au cas où il serait utile à quelqu'un d'autre dans cette situation, mais il ne gère toujours pas la possibilité d'effectuer les appels suivants aux méthodes d'extension.
Requête linq du framework d'entité Include () plusieurs entités enfants
Non, vous ne pouvez pas faire cela dans la cartographie . Une solution de contournement typique est une méthode d'extension simple:
public static IQueryable<Car> BuildCar(this IQueryable<Car> 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 Car
avec toutes les relations, vous allez simplement faire:
var query = from car in db.Cars.BuildCar()
where car.Make == "Ford"
select car;
Modifier:
Vous ne pouvez pas imbriquer les appels de cette façon. Incluez des travaux sur l'entité principale 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<Car>
Et vous ne pouvez pas appeler la méthode d'extension pour IQueryable<Engine>
. Vous devez recommencer avec Car
:
public static IQueryable<Car> BuildCarWheels(this IQuerable<Car> query) {
// This also answers how to eager load nested collections
// Btw. only Select is supported - you cannot use Where, OrderBy or anything else
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<Car> BuildCar(this IQueryable<Car> query) {
return query.BuildCarWheels()
.Include(x => x.Doors)
.Include(x => x.Engine)
.Include(x => x.Bumper)
.Include(x => x.Windows);
}
L'utilisation n'appelle pas Include(x => x.Wheels)
car elle doit être ajoutée automatiquement lorsque vous demandez un chargement rapide de ses entités imbriquées.
Méfiez-vous des requêtes complexes produites par de telles structures de chargement avides et complexes. Cela peut entraîner de très mauvaises performances et beaucoup de données en double transférées de la base de données.
J'ai eu le même problème et j'ai vu l'autre lien qui mentionnait un attribut Include
. Ma solution suppose que vous avez créé un attribut appelé IncludeAttribute
. Avec la méthode d'extension suivante et méthode utilitaire :
public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery)
{
Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
foreach (var prop in typeof(T).GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(IncludeAttribute))))
{
Func<IQueryable<T>, IQueryable<T>> chainedIncludeFunc = f => f.Include(prop.Name);
includeFunc = Compose(includeFunc, chainedIncludeFunc);
}
return includeFunc(originalQuery);
}
private static Func<T, T> Compose<T>(Func<T, T> innerFunc, Func<T, T> outerFunc)
{
return arg => outerFunc(innerFunc(arg));
}