1
votes

Dans EF Core, comment résoudre une référence circulaire de chaîne Inclure ThenInclude?

Considérez le scénario suivant. J'ai 3 classes, représentant une relation plusieurs-à-plusieurs (N-à-N) entre l'étudiant et le sujet:

// context being DbContext
var res = context.Student.Include(s => s.Grades).ThenInclude(g => g.Subject);
public class Subject
{
    public long Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Grade> Grades { get; set; }
}
public class Grade
{
    public long Id { get; set; }
    public int Value { get; set; }

    public virtual Student Student { get; set; }
    public virtual Subject Subject { get; set; } 
}


Je souhaite récupérer la liste de tous les élèves, avec leurs notes, pour chaque matière. Pour ce faire, j'utilise:

public class Student
{
    public long Id { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public long RegistrationNumber { get; set; }

    public virtual ICollection<Grade> Grades { get; set; }
}

Comme les propriétés sont chargées paresseusement, je m'attendais à ce que chaque sujet contienne uniquement sa propriété "Name". Cependant, après inspection, j'ai trouvé que la liste "Grades" est également établie, avec une liste de toutes les notes attribuées au sujet. Ceci, bien sûr, provoque un cycle d'objets.

Je veux éviter ce référencement circulaire, c'est-à-dire obtenir une liste où la seule propriété d'ensemble de chaque sujet est "Nom". Comment puis-je le faire?


3 commentaires

Utilisez l'instruction select pour récupérer les notes et le sujet de cet utilisateur.


@Jawad mais ensuite je devrais faire un devoir personnalisé Une autre option serait d'utiliser des jointures manuelles, comme Holger l'a suggéré, mais je demandais une solution utilisant les propriétés de navigation.


Pourquoi devez-vous éviter la référence circulaire? Est-ce pour la sérialisation JSON? 1) renvoie une vue au lieu des objets de base de données bruts. 2) filtrer les propriétés de navigation à l'aide d'attributs ou d'options de sérialisation. 3) Annulez les navigations après le chargement ...


3 Réponses :


0
votes

Vous pouvez toujours sélectionner manuellement le nom uniquement, comme

   .GroupBy(x => x.Student)

Mais cela ne fonctionne pas avec les propriétés de navigation et les jointures générées automatiquement entre les tables. Là, c'est «tout ou rien».

Ou vous devez faire la jointure complètement manuellement, sans propriétés de navigation.

Mais votre structure n'est pas si compliquée et pas vulnérable aux circularités. Commencez simplement par la note, avec l'élément d'ancrage au milieu.

  context.Grade.Include(x => Subject).Include(x =>Student)

C'est au moins le moyen le plus simple de charger toute votre structure et peut être une approche pour commencer point pour les jointures manuelles.

Vous pouvez peut-être ajouter un

   context.Student.Select(x => x.Name);

Pour vous rapprocher de votre liste d'élèves.

Vous ne pouvez pas sauter le "chargement" de la collection, car ce sont les notes, qui sont chargées en premier. Il y a donc d'abord les éléments de la collection, puis l'entité sujet. Cela n'a aucun sens de ne pas mettre les données dans la collection.


3 commentaires

Vous suggérez de sélectionner tous les noms des élèves. Peut-être que je ne me suis pas exprimé correctement, mais je souhaite obtenir une liste de tous les élèves, où chaque élève aura une liste de ses notes, avec le nom du sujet de chaque niveau accessible par la propriété Subject de ladite note. Et je demande s'il existe un moyen de le faire sans utiliser la jointure manuelle, c'est-à-dire en empêchant le chargement des notes au niveau du sujet.


Oui, j'ai compris cela, et je vous dis qu'il n'y a pas un tel moyen. Avec l'inclusion, vous évitez le chargement différé et effectuez un chargement immédiat. Le "include" désactive le chargement paresseux - c'est le sens. Bien sûr, vous pouvez utiliser le chargement paresseux et accéder simplement au sujet. Cela fonctionne bien et est facile. Mais cela soulèvera une requête par objet. Ce n'est pas la meilleure performance.


Ajout de quelque chose à la réponse.



0
votes

Si vous utilisez l'API MVC / Web asp.net core 3.0, suivez simplement les étapes ci-dessous pour surmonter la référence circulaire en utilisant NewtonsoftJson .

1.Installez Microsoft.AspNetCore.Mvc.NewtonsoftJson package (la version dépend de votre projet)

services.AddControllersWithViews().AddNewtonsoftJson(x =>
        {
            x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        });

2.Ajoutez le code ci-dessous au démarrage

Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.0.0


0 commentaires

0
votes

Suite aux conseils de Jawad, j'ai fini par utiliser les instructions LINQ Select.

J'ai d'abord écrit quelques DTO:

var res = from student in context.Student
          select new StudentDTO
          {
              Name = student.Name,
              Birthday = student.Birthday,
              RegistrationNumber = student.RegistrationNumber,
              Grades = from grade in student.Grades
                       select new GradeDTO
                       {
                           Value = grade.Value,
                           Subject = new SubjectDTO
                           {
                               Name = grade.Subject.Name
                           }
                       }
          };
public class SubjectDTO
{
    public string Name { get; set; }

    public virtual IEnumerable<GradeDTO> Grades { get; set; }
}
public class GradeDTO
{
    public int Value { get; set; }

    public virtual StudentDTO Student { get; set; }
    public virtual SubjectDTO Subject { get; set; }

}


0 commentaires