Comme indiqué dans les commentaires, la première étape consiste à désactiver le chargement paresseux. Vous pouvez le faire en supprimant le modificateur virtual
des propriétés de collection, ce qui est permanent, ou en le désactivant par instance de contexte, ce qui est temporaire :
context.Configuration.ProxyCreationEnabled = false;
(désactiver la création de proxy désactive également le chargement paresseux, mais rend les objets générés plus légers).
Dans les scénarios déconnectés, comme Web AIP2 (comme vous l'avez initialement tagué la question), les gens préfèrent souvent désactiver le chargement paresseux par défaut, en raison de cette cascade sérialiseur-chargement paresseux.
Cependant, vous ne pouvez pas arrêter Entity Framework d'exécuter la résolution des associations. Charger un Produit
le lie au contexte. En incluant ses catégories, vous les liez au contexte et EF remplit leurs collections de Produits
avec le produit attaché, que vous le vouliez ou non.
Vous pouvez réduire quelque peu cet effet en récupérant les produits avec AsNoTracking
(ce qui empêche les entités d'être attachées, c'est-à-dire suivies de leur évolution) :
return db.Produits.AsNoTracking()
.Include(p => p.Catégorie);
Maintenant, les catégories auront uniquement leurs Produits
remplis des Produits
dont ils sont la catégorie.
En passant, dans les scénarios déconnectés, l'utilisation de AsNoTracking
est également préférable. Les entités ne seront de toute façon jamais enregistrées par la même instance de contexte et cela améliore les performances.
Solutions
- Retourner des DTO, pas des types d'entité
En utilisant des objets DTO, vous avez un contrôle total sur le graphique d'objets qui sera sérialisé. Le chargement paresseux ne vous surprendra pas. Mais oui, la quantité de classes DTO requises peut être écrasante.
- Retourner des types anonymes.
Cela peut surprendre car nous ne devrions jamais retourner de types anonymes à partir de méthodes, n'est-ce pas ? Eh bien, ils quittent une méthode d'action sous forme de chaîne Json, tout comme les types nommés, et le client JavaScript ne fait pas la distinction. On pourrait dire que cela rapproche l'environnement JavaScript faiblement typé d'un pas. La seule chose est qu'un type DTO nommé sert de contrat de données (pour ainsi dire) et les types anonymes peuvent être modifiés (trop) facilement et casser le code côté client. Mais nous testons toujours tout unitairement, n'est-ce pas ? Hmm, c'est une option viable dans les petites équipes de développement.
- Personnaliser le sérialiseur.
Vous pouvez indiquer au sérialiseur Json.Net d'ignorer les boucles de référence. En utilisant JsonConvert
directement, cela ressemble à ceci :
var produits = db.Produits.AsNoTracking().Include(p => p.Catégorie);
var paramètre = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented, // Juste pour les humains
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var json = JsonConvert.SerializeObject(produits, paramètre);
En combinant cela avec AsNoTracking()
, cela sérialisera les catégories avec des tableaux Produits
vides ("Produits": []
), car Produit - Catégorie - Produit
est une boucle de référence.
Dans Web API, il existe plusieurs façons de configurer le sérialiseur Json.Net intégré, vous voudrez le faire par méthode d'action.
Personnellement, je préfère utiliser des DTO. J'aime avoir le contrôle (aussi sur les propriétés qui traversent le réseau) et je n'aime pas particulièrement dépendre d'un sérialiseur pour résoudre ce que j'ai négligé de faire.