Tutoriel : Lire les données associées - ASP.NET MVC avec EF CoreTutorial: Read related data - ASP.NET MVC with EF Core

Dans le didacticiel précédent, vous avez élaboré le modèle de données School.In the previous tutorial, you completed the School data model. Dans ce didacticiel, vous allez lire et afficher les données associées, à savoir les données qu’Entity Framework charge dans les propriétés de navigation.In this tutorial, you'll read and display related data -- that is, data that the Entity Framework loads into navigation properties.

Les illustrations suivantes montrent les pages que vous allez utiliser.The following illustrations show the pages that you'll work with.

Page d’index des cours

Page d’index des formateurs

Dans ce didacticiel, vous avez effectué les actions suivantes :In this tutorial, you:

  • Découvrir comment charger les données associéesLearn how to load related data
  • Créer une page CoursesCreate a Courses page
  • Créer une page InstructorsCreate an Instructors page
  • En savoir plus sur le chargement expliciteLearn about explicit loading

PrérequisPrerequisites

Il existe plusieurs façons de permettre à un logiciel de mappage relationnel objet (ORM) comme Entity Framework de charger les données associées dans les propriétés de navigation d’une entité :There are several ways that Object-Relational Mapping (ORM) software such as Entity Framework can load related data into the navigation properties of an entity:

  • Chargement hâtif.Eager loading. Quand l’entité est lue, ses données associées sont également récupérées.When the entity is read, related data is retrieved along with it. Cela génère en général une requête de jointure unique qui récupère toutes les données nécessaires.This typically results in a single join query that retrieves all of the data that's needed. Vous spécifiez un chargement hâtif dans Entity Framework Core à l’aide des méthodes Include et ThenInclude.You specify eager loading in Entity Framework Core by using the Include and ThenInclude methods.

    Exemple de chargement hâtif

    Vous pouvez récupérer une partie des données dans des requêtes distinctes et EF « corrige » les propriétés de navigation.You can retrieve some of the data in separate queries, and EF "fixes up" the navigation properties. Autrement dit, EF ajoute automatiquement les entités récupérées séparément là où elles doivent figurer dans les propriétés de navigation des entités précédemment récupérées.That is, EF automatically adds the separately retrieved entities where they belong in navigation properties of previously retrieved entities. Pour la requête qui récupère les données associées, vous pouvez utiliser la méthode Load à la place d’une méthode renvoyant une liste ou un objet, telle que ToList ou Single.For the query that retrieves related data, you can use the Load method instead of a method that returns a list or object, such as ToList or Single.

    Exemple de requêtes distinctes

  • Chargement explicite.Explicit loading. Quand l’entité est lue pour la première fois, les données associées ne sont pas récupérées.When the entity is first read, related data isn't retrieved. Vous écrivez un code qui récupère les données associées si elles sont nécessaires.You write code that retrieves the related data if it's needed. Comme dans le cas du chargement hâtif avec des requêtes distinctes, le chargement explicite génère plusieurs requêtes envoyées à la base de données.As in the case of eager loading with separate queries, explicit loading results in multiple queries sent to the database. La différence tient au fait qu’avec le chargement explicite, le code spécifie les propriétés de navigation à charger.The difference is that with explicit loading, the code specifies the navigation properties to be loaded. Dans Entity Framework Core 1.1, vous pouvez utiliser la méthode Load pour effectuer le chargement explicite.In Entity Framework Core 1.1 you can use the Load method to do explicit loading. Par exemple :For example:

    Exemple de chargement explicite

  • Chargement différé.Lazy loading. Quand l’entité est lue pour la première fois, les données associées ne sont pas récupérées.When the entity is first read, related data isn't retrieved. Toutefois, la première fois que vous essayez d’accéder à une propriété de navigation, les données requises pour cette propriété de navigation sont récupérées automatiquement.However, the first time you attempt to access a navigation property, the data required for that navigation property is automatically retrieved. Une requête est envoyée à la base de données chaque fois que vous essayez d’obtenir des données à partir d’une propriété de navigation pour la première fois.A query is sent to the database each time you try to get data from a navigation property for the first time. Entity Framework Core 1.0 ne prend pas en charge le chargement différé.Entity Framework Core 1.0 doesn't support lazy loading.

Considérations sur les performancesPerformance considerations

Si vous savez que vous avez besoin des données associées pour toutes les entités récupérées, le chargement hâtif souvent offre des performances optimales, car une seule requête envoyée à la base de données est généralement plus efficace que les requêtes distinctes pour chaque entité récupérée.If you know you need related data for every entity retrieved, eager loading often offers the best performance, because a single query sent to the database is typically more efficient than separate queries for each entity retrieved. Par exemple, supposons que chaque département a dix cours associés.For example, suppose that each department has ten related courses. Le chargement hâtif de toutes les données associées générerait une seule requête (de jointure) et un seul aller-retour à la base de données.Eager loading of all related data would result in just a single (join) query and a single round trip to the database. Une requête distincte pour les cours pour chaque département entraînerait onze allers-retours à la base de données.A separate query for courses for each department would result in eleven round trips to the database. Les allers-retours supplémentaires à la base de données sont particulièrement nuisibles pour les performances lorsque la latence est élevée.The extra round trips to the database are especially detrimental to performance when latency is high.

En revanche, dans certains scénarios, les requêtes distinctes s’avèrent plus efficaces.On the other hand, in some scenarios separate queries is more efficient. Le chargement hâtif de toutes les données associées dans une seule requête peut entraîner une jointure très complexe à générer, que SQL Server ne peut pas traiter efficacement.Eager loading of all related data in one query might cause a very complex join to be generated, which SQL Server can't process efficiently. Ou, si vous avez besoin d’accéder aux propriétés de navigation d’entité uniquement pour un sous-ensemble des entités que vous traitez, des requêtes distinctes peuvent être plus performantes, car le chargement hâtif de tous les éléments en amont entraînerait la récupération de plus de données qu’il vous faut.Or if you need to access an entity's navigation properties only for a subset of a set of the entities you're processing, separate queries might perform better because eager loading of everything up front would retrieve more data than you need. Si les performances sont essentielles, il est préférable de tester les performances des deux façons afin d’effectuer le meilleur choix.If performance is critical, it's best to test performance both ways in order to make the best choice.

Créer une page CoursesCreate a Courses page

L’entité Course inclut une propriété de navigation qui contient l’entité Department du service auquel le cours est affecté.The Course entity includes a navigation property that contains the Department entity of the department that the course is assigned to. Pour afficher le nom du service affecté dans une liste de cours, vous devez obtenir la propriété Name de l’entité Department qui figure dans la propriété de navigation Course.Department.To display the name of the assigned department in a list of courses, you need to get the Name property from the Department entity that's in the Course.Department navigation property.

Créez un contrôleur nommé CoursesController pour le type d’entité Course, en utilisant les mêmes options pour le générateur de modèles automatique Contrôleur MVC avec vues, utilisant Entity Framework que vous avez utilisées précédemment pour le contrôleur Students, comme indiqué dans l’illustration suivante :Create a controller named CoursesController for the Course entity type, using the same options for the MVC Controller with views, using Entity Framework scaffolder that you did earlier for the Students controller, as shown in the following illustration:

Ajouter un contrôleur Courses

Ouvrez CoursesController.cs et examinez la méthode Index.Open CoursesController.cs and examine the Index method. La génération de modèles automatique a spécifié un chargement hâtif pour la propriété de navigation Department à l’aide de la méthode Include.The automatic scaffolding has specified eager loading for the Department navigation property by using the Include method.

Remplacez la méthode Index par le code suivant qui utilise un nom plus approprié pour IQueryable qui renvoie les entités Course (courses à la place de schoolContext) :Replace the Index method with the following code that uses a more appropriate name for the IQueryable that returns Course entities (courses instead of schoolContext):

public async Task<IActionResult> Index()
{
    var courses = _context.Courses
        .Include(c => c.Department)
        .AsNoTracking();
    return View(await courses.ToListAsync());
}

Ouvrez Views/Courses/Index.cshtml et remplacez le code de modèle par le code suivant.Open Views/Courses/Index.cshtml and replace the template code with the following code. Les modifications apparaissent en surbrillance :The changes are highlighted:

@model IEnumerable<ContosoUniversity.Models.Course>

@{
    ViewData["Title"] = "Courses";
}

<h2>Courses</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.CourseID)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Credits)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Department)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.CourseID)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Credits)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Department.Name)
                </td>
                <td>
                    <a asp-action="Edit" asp-route-id="@item.CourseID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.CourseID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.CourseID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Vous avez apporté les modifications suivantes au code généré automatiquement :You've made the following changes to the scaffolded code:

  • Changement de l’en-tête : Index a été remplacé par Courses.Changed the heading from Index to Courses.

  • Ajout d’une colonne Number qui affiche la valeur de la propriété CourseID.Added a Number column that shows the CourseID property value. Par défaut, les clés primaires ne sont pas générées automatiquement, car elles ne sont normalement pas significatives pour les utilisateurs finaux.By default, primary keys aren't scaffolded because normally they're meaningless to end users. Toutefois, dans le cas présent, la clé primaire est significative et vous voulez l’afficher.However, in this case the primary key is meaningful and you want to show it.

  • Modification de la colonne Department afin d’afficher le nom du département.Changed the Department column to display the department name. Le code affiche la propriété Name de l’entité Department qui est chargée dans la propriété de navigation Department :The code displays the Name property of the Department entity that's loaded into the Department navigation property:

    @Html.DisplayFor(modelItem => item.Department.Name)
    

Exécutez l’application et sélectionnez l’onglet Courses pour afficher la liste avec les noms des départements.Run the app and select the Courses tab to see the list with department names.

Page d’index des cours

Créer une page InstructorsCreate an Instructors page

Dans cette section, vous allez créer un contrôleur et une vue pour l’entité Instructor afin d’afficher la page Instructors :In this section, you'll create a controller and view for the Instructor entity in order to display the Instructors page:

Page d’index des formateurs

Cette page lit et affiche les données associées comme suit :This page reads and displays related data in the following ways:

  • La liste des formateurs affiche les données associées de l’entité OfficeAssignment.The list of instructors displays related data from the OfficeAssignment entity. Il existe une relation un-à-zéro-ou-un entre les entités Instructor et OfficeAssignment.The Instructor and OfficeAssignment entities are in a one-to-zero-or-one relationship. Vous allez utiliser un chargement hâtif pour les entités OfficeAssignment.You'll use eager loading for the OfficeAssignment entities. Comme expliqué précédemment, le chargement hâtif est généralement plus efficace lorsque vous avez besoin des données associées pour toutes les lignes extraites de la table primaire.As explained earlier, eager loading is typically more efficient when you need the related data for all retrieved rows of the primary table. Dans ce cas, vous souhaitez afficher les affectations de bureaux pour tous les formateurs affichés.In this case, you want to display office assignments for all displayed instructors.

  • Lorsque l’utilisateur sélectionne un formateur, les entités Course associées sont affichées.When the user selects an instructor, related Course entities are displayed. Il existe une relation plusieurs à plusieurs entre les entités Instructor et Course.The Instructor and Course entities are in a many-to-many relationship. Vous allez utiliser un chargement hâtif pour les entités Course et les entités Department qui leur sont associées.You'll use eager loading for the Course entities and their related Department entities. Dans ce cas, des requêtes distinctes peuvent être plus efficaces, car vous avez besoin de cours uniquement pour le formateur sélectionné.In this case, separate queries might be more efficient because you need courses only for the selected instructor. Toutefois, cet exemple montre comment utiliser le chargement hâtif pour des propriétés de navigation dans des entités qui se trouvent elles-mêmes dans des propriétés de navigation.However, this example shows how to use eager loading for navigation properties within entities that are themselves in navigation properties.

  • Lorsque l’utilisateur sélectionne un cours, les données associées dans le jeu d’entités Enrollment s’affichent.When the user selects a course, related data from the Enrollments entity set is displayed. Il existe une relation un-à-plusieurs entre les entités Course et Enrollment.The Course and Enrollment entities are in a one-to-many relationship. Vous allez utiliser des requêtes distinctes pour les entités Enrollment et les entités Student qui leur sont associées.You'll use separate queries for Enrollment entities and their related Student entities.

Créer un modèle de vue pour la vue d’index des formateursCreate a view model for the Instructor Index view

La page Instructors affiche des données de trois tables différentes.The Instructors page shows data from three different tables. Par conséquent, vous allez créer un modèle de vue qui comprend trois propriétés, chacune contenant les données d’une des tables.Therefore, you'll create a view model that includes three properties, each holding the data for one of the tables.

Dans le dossier SchoolViewModels, créez InstructorIndexData.cs et remplacez le code existant par le code suivant :In the SchoolViewModels folder, create InstructorIndexData.cs and replace the existing code with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class InstructorIndexData
    {
        public IEnumerable<Instructor> Instructors { get; set; }
        public IEnumerable<Course> Courses { get; set; }
        public IEnumerable<Enrollment> Enrollments { get; set; }
    }
}

Créer les vues et le contrôleur de formateursCreate the Instructor controller and views

Créez un contrôleur de formateurs avec des actions de lecture/écriture EF comme indiqué dans l’illustration suivante :Create an Instructors controller with EF read/write actions as shown in the following illustration:

Ajouter le contrôleur de formateurs

Ouvrez InstructorsController.cs et ajoutez une instruction using pour l’espace de noms ViewModel :Open InstructorsController.cs and add a using statement for the ViewModels namespace:

using ContosoUniversity.Models.SchoolViewModels;

Remplacez la méthode Index par le code suivant pour effectuer un chargement hâtif des données associées et le placer dans le modèle de vue.Replace the Index method with the following code to do eager loading of related data and put it in the view model.

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Enrollments)
                    .ThenInclude(i => i.Student)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .AsNoTracking()
          .OrderBy(i => i.LastName)
          .ToListAsync();
    
    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["CourseID"] = courseID.Value;
        viewModel.Enrollments = viewModel.Courses.Where(
            x => x.CourseID == courseID).Single().Enrollments;
    }

    return View(viewModel);
}

La méthode accepte des données de route facultatives (id) et un paramètre de chaîne de requête (courseID) qui fournissent les valeurs d’ID du formateur sélectionné et du cours sélectionné.The method accepts optional route data (id) and a query string parameter (courseID) that provide the ID values of the selected instructor and selected course. Ces paramètres sont fournis par les liens hypertexte Select dans la page.The parameters are provided by the Select hyperlinks on the page.

Le code commence par créer une instance du modèle de vue et la placer dans la liste des formateurs.The code begins by creating an instance of the view model and putting in it the list of instructors. Le code spécifie un chargement hâtif pour les propriétés de navigation Instructor.OfficeAssignment et Instructor.CourseAssignments.The code specifies eager loading for the Instructor.OfficeAssignment and the Instructor.CourseAssignments navigation properties. Dans la propriété CourseAssignments, la propriété Course est chargée et, dans ce cadre, les propriétés Enrollments et Department sont chargées, et dans chaque entité Enrollment, la propriété Student est chargée.Within the CourseAssignments property, the Course property is loaded, and within that, the Enrollments and Department properties are loaded, and within each Enrollment entity the Student property is loaded.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Étant donné que la vue nécessite toujours l’entité OfficeAssignment, il est plus efficace de l’extraire dans la même requête.Since the view always requires the OfficeAssignment entity, it's more efficient to fetch that in the same query. Les entités Course sont requises lorsqu’un formateur est sélectionné dans la page web, de sorte qu’une requête individuelle est meilleure que plusieurs requêtes seulement si la page s’affiche plus souvent avec un cours sélectionné que sans.Course entities are required when an instructor is selected in the web page, so a single query is better than multiple queries only if the page is displayed more often with a course selected than without.

Le code répète CourseAssignments et Course, car vous avez besoin de deux propriétés de Course.The code repeats CourseAssignments and Course because you need two properties from Course. La première chaîne d’appels ThenInclude obtient CourseAssignment.Course, Course.Enrollments et Enrollment.Student.The first string of ThenInclude calls gets CourseAssignment.Course, Course.Enrollments, and Enrollment.Student.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

À ce stade dans le code, un autre ThenInclude serait pour les propriétés de navigation de Student, dont vous n’avez pas besoin.At that point in the code, another ThenInclude would be for navigation properties of Student, which you don't need. Toutefois, l’appel de Include recommence avec les propriétés Instructor, donc vous devez parcourir la chaîne à nouveau, cette fois en spécifiant Course.Department à la place de Course.Enrollments.But calling Include starts over with Instructor properties, so you have to go through the chain again, this time specifying Course.Department instead of Course.Enrollments.

viewModel.Instructors = await _context.Instructors
      .Include(i => i.OfficeAssignment)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Enrollments)
                .ThenInclude(i => i.Student)
      .Include(i => i.CourseAssignments)
        .ThenInclude(i => i.Course)
            .ThenInclude(i => i.Department)
      .AsNoTracking()
      .OrderBy(i => i.LastName)
      .ToListAsync();

Le code suivant s’exécute quand un formateur a été sélectionné.The following code executes when an instructor was selected. Le formateur sélectionné est récupéré à partir de la liste des formateurs dans le modèle de vue.The selected instructor is retrieved from the list of instructors in the view model. La propriété Courses du modèle de vue est alors chargée avec les entités Course de la propriété de navigation CourseAssignments de ce formateur.The view model's Courses property is then loaded with the Course entities from that instructor's CourseAssignments navigation property.

if (id != null)
{
    ViewData["InstructorID"] = id.Value;
    Instructor instructor = viewModel.Instructors.Where(
        i => i.ID == id.Value).Single();
    viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

La méthode Where renvoie une collection, mais dans ce cas, les critères transmis à cette méthode entraînent le renvoi d’une seule entité Instructor.The Where method returns a collection, but in this case the criteria passed to that method result in only a single Instructor entity being returned. La méthode Single convertit la collection en une seule entité Instructor, ce qui vous permet d’accéder à la propriété CourseAssignments de cette entité.The Single method converts the collection into a single Instructor entity, which gives you access to that entity's CourseAssignments property. La propriété CourseAssignments contient des entités CourseAssignment, à partir desquelles vous souhaitez uniquement les entités Course associées.The CourseAssignments property contains CourseAssignment entities, from which you want only the related Course entities.

Vous utilisez la méthode Single sur une collection lorsque vous savez que la collection aura un seul élément.You use the Single method on a collection when you know the collection will have only one item. La méthode Single lève une exception si la collection transmise est vide ou s’il y a plusieurs éléments.The Single method throws an exception if the collection passed to it's empty or if there's more than one item. Une alternative est SingleOrDefault, qui renvoie une valeur par défaut (Null dans ce cas) si la collection est vide.An alternative is SingleOrDefault, which returns a default value (null in this case) if the collection is empty. Toutefois, dans ce cas, cela entraînerait encore une exception (en tentant de trouver une propriété Courses sur une référence null) et le message d’exception indiquerait moins clairement la cause du problème.However, in this case that would still result in an exception (from trying to find a Courses property on a null reference), and the exception message would less clearly indicate the cause of the problem. Lorsque vous appelez la méthode Single, vous pouvez également transmettre la condition Where au lieu d’appeler séparément la méthode Where :When you call the Single method, you can also pass in the Where condition instead of calling the Where method separately:

.Single(i => i.ID == id.Value)

À la place de :Instead of:

.Where(i => i.ID == id.Value).Single()

Ensuite, si un cours a été sélectionné, le cours sélectionné est récupéré à partir de la liste des cours dans le modèle de vue.Next, if a course was selected, the selected course is retrieved from the list of courses in the view model. Ensuite, la propriété Enrollments du modèle de vue est chargée avec les entités Enrollment à partir de la propriété de navigation Enrollments de ce cours.Then the view model's Enrollments property is loaded with the Enrollment entities from that course's Enrollments navigation property.

if (courseID != null)
{
    ViewData["CourseID"] = courseID.Value;
    viewModel.Enrollments = viewModel.Courses.Where(
        x => x.CourseID == courseID).Single().Enrollments;
}

Modifier la vue d’index des formateursModify the Instructor Index view

Dans Views/Instructors/Index.cshtml, remplacez le code du modèle par le code suivant.In Views/Instructors/Index.cshtml, replace the template code with the following code. Les modifications apparaissent en surbrillance.The changes are highlighted.

@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

@{
    ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>Last Name</th>
            <th>First Name</th>
            <th>Hire Date</th>
            <th>Office</th>
            <th>Courses</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Instructors)
        {
            string selectedRow = "";
            if (item.ID == (int?)ViewData["InstructorID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FirstMidName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.HireDate)
                </td>
                <td>
                    @if (item.OfficeAssignment != null)
                    {
                        @item.OfficeAssignment.Location
                    }
                </td>
                <td>
                    @{
                        foreach (var course in item.CourseAssignments)
                        {
                            @course.Course.CourseID @:  @course.Course.Title <br />
                        }
                    }
                </td>
                <td>
                    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
           }
    </tbody>
</table>

Vous avez apporté les modifications suivantes au code existant :You've made the following changes to the existing code:

  • Vous avez changé la classe de modèle en InstructorIndexData.Changed the model class to InstructorIndexData.

  • Vous avez changé le titre de la page en remplaçant Index par Instructors.Changed the page title from Index to Instructors.

  • Vous avez ajouté une colonne Office qui affiche item.OfficeAssignment.Location seulement si item.OfficeAssignment n’est pas Null.Added an Office column that displays item.OfficeAssignment.Location only if item.OfficeAssignment isn't null. (Comme il s’agit d’une relation un-à-zéro-ou-un, il se peut qu’il n’y ait pas d’entité OfficeAssignment associée.)(Because this is a one-to-zero-or-one relationship, there might not be a related OfficeAssignment entity.)

    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
    
  • Vous avez ajouté une colonne Courses qui affiche les cours animés par chaque formateur.Added a Courses column that displays courses taught by each instructor. Pour plus d’informations, consultez la section Conversion de ligne explicite avec @ : de l’article relatif à la syntaxe Razor.For more information, see the Explicit line transition with @: section of the Razor syntax article.

  • Vous avez ajouté un code qui ajoute dynamiquement class="success" à l’élément tr du formateur sélectionné.Added code that dynamically adds class="success" to the tr element of the selected instructor. Cela définit une couleur d’arrière-plan pour la ligne sélectionnée à l’aide d’une classe d’amorçage.This sets a background color for the selected row using a Bootstrap class.

    string selectedRow = "";
    if (item.ID == (int?)ViewData["InstructorID"])
    {
        selectedRow = "success";
    }
    <tr class="@selectedRow">
    
  • Vous avez ajouté un nouveau lien hypertexte étiqueté Select immédiatement avant les autres liens dans chaque ligne, ce qui entraîne l’envoi de l’ID du formateur sélectionné à la méthode Index.Added a new hyperlink labeled Select immediately before the other links in each row, which causes the selected instructor's ID to be sent to the Index method.

    <a asp-action="Index" asp-route-id="@item.ID">Select</a> |
    

Exécutez l’application et sélectionnez l’onglet Instructors. La page affiche la propriété Location des entités OfficeAssignment associées et une cellule de table vide lorsqu’il n’existe aucune entité OfficeAssignment associée.Run the app and select the Instructors tab. The page displays the Location property of related OfficeAssignment entities and an empty table cell when there's no related OfficeAssignment entity.

Page d’index des formateurs sans aucun élément sélectionné

Dans le fichier Views/Instructors/Index.cshtml, après l’élément de fermeture de table (à la fin du fichier), ajoutez le code suivant.In the Views/Instructors/Index.cshtml file, after the closing table element (at the end of the file), add the following code. Ce code affiche la liste des cours associés à un formateur quand un formateur est sélectionné.This code displays a list of courses related to an instructor when an instructor is selected.

@if (Model.Courses != null)
{
    <h3>Courses Taught by Selected Instructor</h3>
    <table class="table">
        <tr>
            <th></th>
            <th>Number</th>
            <th>Title</th>
            <th>Department</th>
        </tr>

        @foreach (var item in Model.Courses)
        {
            string selectedRow = "";
            if (item.CourseID == (int?)ViewData["CourseID"])
            {
                selectedRow = "success";
            }
            <tr class="@selectedRow">
                <td>
                    @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
                </td>
                <td>
                    @item.CourseID
                </td>
                <td>
                    @item.Title
                </td>
                <td>
                    @item.Department.Name
                </td>
            </tr>
        }

    </table>
}

Ce code lit la propriété Courses du modèle de vue pour afficher la liste des cours.This code reads the Courses property of the view model to display a list of courses. Il fournit également un lien hypertexte Select qui envoie l’ID du cours sélectionné à la méthode d’action Index.It also provides a Select hyperlink that sends the ID of the selected course to the Index action method.

Actualisez la page et sélectionnez un formateur.Refresh the page and select an instructor. Vous voyez à présent une grille qui affiche les cours affectés au formateur sélectionné et, pour chaque cours, vous voyez le nom du département affecté.Now you see a grid that displays courses assigned to the selected instructor, and for each course you see the name of the assigned department.

Page d’index des formateurs avec un formateur sélectionné

Après le bloc de code que vous venez d’ajouter, ajoutez le code suivant.After the code block you just added, add the following code. Ceci affiche la liste des étudiants qui sont inscrits à un cours quand ce cours est sélectionné.This displays a list of the students who are enrolled in a course when that course is selected.

@if (Model.Enrollments != null)
{
    <h3>
        Students Enrolled in Selected Course
    </h3>
    <table class="table">
        <tr>
            <th>Name</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        {
            <tr>
                <td>
                    @item.Student.FullName
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
}

Ce code lit la propriété Enrollments du modèle de vue pour afficher la liste des étudiants inscrits dans ce cours.This code reads the Enrollments property of the view model in order to display a list of students enrolled in the course.

Actualisez la page à nouveau et sélectionnez un formateur.Refresh the page again and select an instructor. Ensuite, sélectionnez un cours pour afficher la liste des étudiants inscrits et leurs notes.Then select a course to see the list of enrolled students and their grades.

Page d’index des formateurs avec un formateur et un cours sélectionnés

À propos du chargement expliciteAbout explicit loading

Lorsque vous avez récupéré la liste des formateurs dans InstructorsController.cs, vous avez spécifié un chargement hâtif pour la propriété de navigation CourseAssignments.When you retrieved the list of instructors in InstructorsController.cs, you specified eager loading for the CourseAssignments navigation property.

Supposons que vous vous attendiez à ce que les utilisateurs ne souhaitent que rarement voir les inscriptions pour un formateur et un cours sélectionnés.Suppose you expected users to only rarely want to see enrollments in a selected instructor and course. Dans ce cas, vous pouvez charger les données d’inscription uniquement si elles sont demandées.In that case, you might want to load the enrollment data only if it's requested. Pour voir un exemple illustrant comment effectuer un chargement explicite, remplacez la méthode Index par le code suivant, qui supprime le chargement hâtif pour Enrollments et charge explicitement cette propriété.To see an example of how to do explicit loading, replace the Index method with the following code, which removes eager loading for Enrollments and loads that property explicitly. Les modifications du code apparaissent en surbrillance.The code changes are highlighted.

public async Task<IActionResult> Index(int? id, int? courseID)
{
    var viewModel = new InstructorIndexData();
    viewModel.Instructors = await _context.Instructors
          .Include(i => i.OfficeAssignment)
          .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
                .ThenInclude(i => i.Department)
          .OrderBy(i => i.LastName)
          .ToListAsync();

    if (id != null)
    {
        ViewData["InstructorID"] = id.Value;
        Instructor instructor = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single();
        viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
    }

    if (courseID != null)
    {
        ViewData["CourseID"] = courseID.Value;
        var selectedCourse = viewModel.Courses.Where(x => x.CourseID == courseID).Single();
        await _context.Entry(selectedCourse).Collection(x => x.Enrollments).LoadAsync();
        foreach (Enrollment enrollment in selectedCourse.Enrollments)
        {
            await _context.Entry(enrollment).Reference(x => x.Student).LoadAsync();
        }
        viewModel.Enrollments = selectedCourse.Enrollments;
    }

    return View(viewModel);
}

Le nouveau code supprime les appels de la méthode ThenInclude pour les données d’inscription à partir du code qui extrait les entités de formateur.The new code drops the ThenInclude method calls for enrollment data from the code that retrieves instructor entities. Il dépose également AsNoTracking.It also drops AsNoTracking. Si un formateur et un cours sont sélectionnés, le code en surbrillance récupère les entités Enrollment pour le cours sélectionné et les entités Student pour chaque entité Enrollment.If an instructor and course are selected, the highlighted code retrieves Enrollment entities for the selected course, and Student entities for each Enrollment.

Exécutez l’application, accédez à la page d’index des formateurs et vous ne verrez aucune différence pour ce qui est affiché dans la page, bien que vous ayez modifié la façon dont les données sont récupérées.Run the app, go to the Instructors Index page now and you'll see no difference in what's displayed on the page, although you've changed how the data is retrieved.

Obtenir le codeGet the code

Télécharger ou afficher l’application complète.Download or view the completed application.

Étapes suivantesNext steps

Dans ce didacticiel, vous avez effectué les actions suivantes :In this tutorial, you:

  • Chargement des données associées découvertLearned how to load related data
  • Page Courses crééeCreated a Courses page
  • Page Instructors crééeCreated an Instructors page
  • Chargement explicite découvertLearned about explicit loading

Passez au tutoriel suivant pour découvrir comment mettre à jour les données associées.Advance to the next tutorial to learn how to update related data.