Partager via


Tutoriel : Mettre à jour les données associées avec EF dans une application MVC ASP.NET

Dans le tutoriel précédent, vous avez affiché les données associées. Dans ce tutoriel, vous allez mettre à jour les données associées. Pour la plupart des relations, cela peut être effectué en mettant à jour les champs de clé étrangère ou les propriétés de navigation. Pour les relations plusieurs-à-plusieurs, Entity Framework n’expose pas directement la table de jointure. Vous devez donc ajouter et supprimer des entités dans et à partir des propriétés de navigation appropriées.

Les illustrations suivantes montrent quelques-unes des pages que vous allez utiliser.

Course_create_page

Instructor_edit_page_with_courses

Modification de l’instructeur avec des cours

Dans ce didacticiel, vous avez effectué les actions suivantes :

  • Personnaliser les pages des cours
  • Page Ajouter un bureau aux instructeurs
  • Ajouter des cours à la page des instructeurs
  • Mettre à jour DeleteConfirmed
  • Ajouter des emplacements de bureau et des cours à la page Create

Prérequis

Personnaliser les pages des cours

Quand une entité de cours est créée, elle doit avoir une relation à un département existant. Pour faciliter cela, le code du modèle généré automatiquement inclut des méthodes de contrôleur, et des vues Create et Edit qui incluent une liste déroulante pour sélectionner le département. La liste déroulante définit la propriété de clé étrangère Course.DepartmentID, qui est tout ce dont Entity Framework a besoin pour charger la propriété de navigation Department avec l’entité Department appropriée. Vous utilisez le code du modèle généré automatiquement, mais que vous modifiez un peu pour ajouter la gestion des erreurs et trier la liste déroulante.

Dans CourseController.cs, supprimez les quatre Create méthodes et Edit remplacez-les par le code suivant :

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CourseID,Title,Credits,DepartmentID")]Course course)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.Courses.Add(course);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    catch (RetryLimitExceededException /* dex */)
    {
        //Log the error (uncomment dex variable name and add a line here to write a log.)
        ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var courseToUpdate = db.Courses.Find(id);
    if (TryUpdateModel(courseToUpdate, "",
       new string[] { "Title", "Credits", "DepartmentID" }))
    {
        try
        {
            db.SaveChanges();

            return RedirectToAction("Index");
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
    return View(courseToUpdate);
}

private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
    var departmentsQuery = from d in db.Departments
                           orderby d.Name
                           select d;
    ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
} 

Ajoutez l’instruction suivante using au début du fichier :

using System.Data.Entity.Infrastructure;

La PopulateDepartmentsDropDownList méthode obtient une liste de tous les services triés par nom, crée une SelectList collection pour une liste déroulante et transmet la collection à la vue dans une ViewBag propriété. La méthode accepte le paramètre facultatif selectedDepartment qui permet au code appelant de spécifier l’élément sélectionné lors de l’affichage de la liste déroulante. La vue passe le nom DepartmentID à l’assistance DropDownList , et l’assistance sait ensuite rechercher dans l’objet ViewBag un SelectList nom DepartmentID.

La HttpGet Create méthode appelle la PopulateDepartmentsDropDownList méthode sans définir l’élément sélectionné, car pour un nouveau cours, le service n’est pas encore établi :

public ActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}

La HttpGet Edit méthode définit l’élément sélectionné, en fonction de l’ID du service déjà affecté au cours en cours de modification :

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Course course = db.Courses.Find(id);
    if (course == null)
    {
        return HttpNotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

Les HttpPost méthodes pour les deux Create et Edit incluent également du code qui définit l’élément sélectionné lorsqu’ils réaffichent la page après une erreur :

catch (RetryLimitExceededException /* dex */)
{
    //Log the error (uncomment dex variable name and add a line here to write a log.)
    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);

Ce code garantit que lorsque la page est réaffichée pour afficher le message d’erreur, le service sélectionné reste sélectionné.

Les affichages Cours sont déjà générés automatiquement avec des listes déroulantes pour le champ de service, mais vous ne souhaitez pas que la légende DepartmentID de ce champ soit modifiée. Faites donc en sorte que la modification suivante apportée au fichier Views\Course\Create.cshtml modifie la légende.

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Course</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CourseID)
                @Html.ValidationMessageFor(model => model.CourseID)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Credits, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Credits)
                @Html.ValidationMessageFor(model => model.Credits)
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="DepartmentID">Department</label>
            <div class="col-md-10">
                @Html.DropDownList("DepartmentID", String.Empty)
                @Html.ValidationMessageFor(model => model.DepartmentID)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Apportez la même modification dans Views\Course\Edit.cshtml.

Normalement, le générateur de modèles automatique ne génère pas de clé primaire, car la valeur de la clé est générée par la base de données et ne peut pas être modifiée et n’est pas une valeur significative à afficher pour les utilisateurs. Pour les entités course, le générateur de modèles automatique inclut une zone de texte pour le CourseID champ, car il comprend que l’attribut signifie que l’utilisateur DatabaseGeneratedOption.None doit être en mesure d’entrer la valeur de clé primaire. Mais il ne comprend pas que, car le nombre est significatif que vous souhaitez le voir dans les autres affichages, vous devez donc l’ajouter manuellement.

Dans Views\Course\Edit.cshtml, ajoutez un champ de numéro de cours avant le champ Titre . Comme il s’agit de la clé primaire, elle est affichée, mais elle ne peut pas être modifiée.

<div class="form-group">
    @Html.LabelFor(model => model.CourseID, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.DisplayFor(model => model.CourseID)
    </div>
</div>

Il existe déjà un champ masqué (Html.HiddenFor assistance) pour le numéro de cours dans l’affichage Édition. L’ajout d’un élément Html.LabelFor helper n’élimine pas le besoin du champ masqué, car il n’entraîne pas l’inclusion du numéro de cours dans les données publiées lorsque l’utilisateur clique sur Enregistrer sur la page Modifier.

Dans Views\Course\Delete.cshtml et Views\Course\Details.cshtml, remplacez la légende du nom du service par « Nom » et ajoutez un champ numéro de cours avant le champ Titre.

<dt>
    Department
</dt>

<dd>
    @Html.DisplayFor(model => model.Department.Name)
</dd>

<dt>
    @Html.DisplayNameFor(model => model.CourseID)
</dt>

<dd>
    @Html.DisplayFor(model => model.CourseID)
</dd>

Exécutez la page Créer (affichez la page Index de cours, puis cliquez sur Créer) et entrez des données pour un nouveau cours :

Valeur Setting
Nombre Entrez 1000.
Titre Entrez algèbre.
Crédits Entrez 4.
Service Sélectionnez Mathématiques.

Cliquez sur Créer. La page Index de cours s’affiche avec le nouveau cours ajouté à la liste. Le nom du département dans la liste de la page Index provient de la propriété de navigation, ce qui montre que la relation a été établie correctement.

Exécutez la page Modifier (affichez la page Index du cours, puis cliquez sur Modifier sur un cours).

Modifiez les données dans la page et cliquez sur Save. La page Index de cours s’affiche avec les données de cours mises à jour.

Page Ajouter un bureau aux instructeurs

Quand vous modifiez un enregistrement de formateur, vous voulez avoir la possibilité de mettre à jour l’attribution du bureau du formateur. L’entité Instructor a une relation un-à-zéro-ou-un avec l’entité OfficeAssignment , ce qui signifie que vous devez gérer les situations suivantes :

  • Si l’utilisateur efface l’attribution du bureau et qu’il a initialement une valeur, vous devez supprimer et supprimer l’entité OfficeAssignment .
  • Si l’utilisateur entre une valeur d’affectation de bureau et qu’il a été vide à l’origine, vous devez créer une entité OfficeAssignment .
  • Si l’utilisateur modifie la valeur d’une affectation de bureau, vous devez modifier la valeur dans une entité existante OfficeAssignment .

Ouvrez InstructorController.cs et examinez la HttpGet Edit méthode :

{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors.Find(id);
    if (instructor == null)
    {
        return HttpNotFound();
    }
    ViewBag.ID = new SelectList(db.OfficeAssignments, "InstructorID", "Location", instructor.ID);
    return View(instructor);
}

Le code généré automatiquement ici n’est pas ce que vous voulez. Il configure des données pour une liste déroulante, mais vous avez besoin d’une zone de texte. Remplacez cette méthode par le code suivant :

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    return View(instructor);
}

Ce code supprime l’instruction ViewBag et ajoute un chargement impatient pour l’entité associée OfficeAssignment . Vous ne pouvez pas effectuer de chargement désireux avec la Find méthode, de sorte que les Where méthodes et Single les méthodes sont utilisées à la place pour sélectionner l’instructeur.

Remplacez la HttpPost Edit méthode par le code suivant. qui gère les mises à jour des affectations de bureau :

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Where(i => i.ID == id)
       .Single();

    if (TryUpdateModel(instructorToUpdate, "",
       new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    {
       try
       {
          if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
          {
             instructorToUpdate.OfficeAssignment = null;
          }

          db.SaveChanges();

          return RedirectToAction("Index");
       }
       catch (RetryLimitExceededException /* dex */)
      {
         //Log the error (uncomment dex variable name and add a line here to write a log.
         ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
      }
   }
   return View(instructorToUpdate);
}

La référence à RetryLimitExceededException exiger une using instruction ; pour l’ajouter - placez votre souris sur RetryLimitExceededException. Le message suivant s’affiche :  Message d’exception de nouvelle tentative

Sélectionnez Afficher les correctifs potentiels, puis utilisez System.Data.Entity.Infrastructure

Résoudre une exception de nouvelle tentative

Le code effectue les actions suivantes :

  • Modifie le nom EditPost de la méthode car la signature est désormais identique à la HttpGet méthode (l’attribut ActionName spécifie que l’URL /Edit/ est toujours utilisée).

  • Obtient l'entité Instructor en cours à partir de la base de données à l’aide d’un chargement hâtif de la propriété de navigation OfficeAssignment. C’est la même chose que ce que vous avez fait dans la HttpGet Edit méthode.

  • Met à jour l’entité Instructor récupérée avec les valeurs du classeur de modèles. La surcharge TryUpdateModel utilisée vous permet de répertorier les propriétés que vous souhaitez inclure. Ceci empêche la survalidation, comme expliqué dans le deuxième didacticiel.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Si l’emplacement du bureau est vide, il définit la propriété Instructor.OfficeAssignment sur null, de façon que la ligne correspondante dans la table OfficeAssignment soit supprimée.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Il enregistre les modifications dans la base de données.

Dans Views\Instructor\Edit.cshtml, après les div éléments du champ Hire Date , ajoutez un nouveau champ pour modifier l’emplacement du bureau :

<div class="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

Exécutez la page (sélectionnez l’onglet Instructeurs , puis cliquez sur Modifier sur un instructeur). Modifiez Office Location et cliquez sur Save.

Ajouter des cours à la page des instructeurs

Les instructeurs peuvent enseigner dans n’importe quel nombre de cours. Vous allez maintenant améliorer la page Modifier l’instructeur en ajoutant la possibilité de modifier les devoirs de cours à l’aide d’un groupe de cases à cocher.

La relation entre les Course entités est Instructor plusieurs-à-plusieurs, ce qui signifie que vous n’avez pas d’accès direct aux propriétés de clé étrangère qui se trouvent dans la table de jointure. Au lieu de cela, vous ajoutez et supprimez des entités à et à partir de la Instructor.Courses propriété de navigation.

L’interface utilisateur qui vous permet de changer les cours auxquels un formateur est affecté est un groupe de cases à cocher. Une case à cocher est affichée pour chaque cours de la base de données, et ceux auxquels le formateur est actuellement affecté sont sélectionnés. L’utilisateur peut cocher ou décocher les cases pour changer les affectations de cours. Si le nombre de cours était beaucoup plus grand, vous voudriez probablement utiliser une autre méthode de présentation des données dans la vue, mais vous utiliseriez la même méthode de manipulation des propriétés de navigation pour créer ou supprimer des relations.

Pour fournir des données à la vue pour la liste de cases à cocher, vous utilisez une classe de modèle de vue. Créez AssignedCourseData.cs dans le dossier ViewModels et remplacez le code existant par le code suivant :

namespace ContosoUniversity.ViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

Dans InstructorController.cs, remplacez la HttpGet Edit méthode par le code suivant. Les modifications sont mises en surbrillance.

public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Instructor instructor = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses)
        .Where(i => i.ID == id)
        .Single();
    if (instructor == null)
    {
        return HttpNotFound();
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

private void PopulateAssignedCourseData(Instructor instructor)
{
    var allCourses = db.Courses;
    var instructorCourses = new HashSet<int>(instructor.Courses.Select(c => c.CourseID));
    var viewModel = new List<AssignedCourseData>();
    foreach (var course in allCourses)
    {
        viewModel.Add(new AssignedCourseData
        {
            CourseID = course.CourseID,
            Title = course.Title,
            Assigned = instructorCourses.Contains(course.CourseID)
        });
    }
    ViewBag.Courses = viewModel;
}

Le code ajoute un chargement hâtif pour la propriété de navigation Courses et appelle la nouvelle méthode PopulateAssignedCourseData pour fournir des informations pour le tableau de cases à cocher avec la classe de modèle de vue AssignedCourseData.

Le code de la méthodePopulateAssignedCourseData lit toutes les entités Course pour charger une liste de cours avec la classe de modèle de vue. Pour chaque cours, le code vérifie s’il existe dans la propriété de navigation Courses du formateur. Pour créer une recherche efficace lors de la vérification de l’affectation d’un cours au formateur, les cours affectés à l’instructeur sont placés dans une collection HashSet . La Assigned propriété est définie true pour les cours auxquels l’instructeur est affecté. La vue utilise cette propriété pour déterminer quelles cases doivent être affichées cochées. Enfin, la liste est passée à l’affichage dans une ViewBag propriété.

Ensuite, ajoutez le code qui est exécuté quand l’utilisateur clique sur Save. Remplacez la EditPost méthode par le code suivant, qui appelle une nouvelle méthode qui met à jour la Courses propriété de navigation de l’entité Instructor . Les modifications sont mises en surbrillance.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int? id, string[] selectedCourses)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var instructorToUpdate = db.Instructors
       .Include(i => i.OfficeAssignment)
       .Include(i => i.Courses)
       .Where(i => i.ID == id)
       .Single();

    if (TryUpdateModel(instructorToUpdate, "",
       new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    {
        try
        {
            if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }

            UpdateInstructorCourses(selectedCourses, instructorToUpdate);

            db.SaveChanges();

            return RedirectToAction("Index");
        }
        catch (RetryLimitExceededException /* dex */)
        {
            //Log the error (uncomment dex variable name and add a line here to write a log.
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
    }
    PopulateAssignedCourseData(instructorToUpdate);
    return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
   if (selectedCourses == null)
   {
      instructorToUpdate.Courses = new List<Course>();
      return;
   }
 
   var selectedCoursesHS = new HashSet<string>(selectedCourses);
   var instructorCourses = new HashSet<int>
       (instructorToUpdate.Courses.Select(c => c.CourseID));
   foreach (var course in db.Courses)
   {
      if (selectedCoursesHS.Contains(course.CourseID.ToString()))
      {
         if (!instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Add(course);
         }
      }
      else
      {
         if (instructorCourses.Contains(course.CourseID))
         {
            instructorToUpdate.Courses.Remove(course);
         }
      }
   }
}

La signature de la méthode est désormais différente de la HttpGet Edit méthode, de sorte que le nom de la méthode passe de EditPost retour à Edit.

Étant donné que la vue n’a pas de collection d’entités Course , le classeur de modèles ne peut pas mettre à jour automatiquement la Courses propriété de navigation. Au lieu d’utiliser le classeur de modèles pour mettre à jour la Courses propriété de navigation, vous allez le faire dans la nouvelle UpdateInstructorCourses méthode. Par conséquent, vous devez exclure la propriété Courses de la liaison de modèle. Cela ne nécessite aucune modification du code qui appelle TryUpdateModel , car vous utilisez la surcharge de liste explicite et Courses n’est pas dans la liste include.

Si aucune case à cocher n’a été cochée, le code dans initialise UpdateInstructorCourses la Courses propriété de navigation avec une collection vide :

if (selectedCourses == null)
{
    instructorToUpdate.Courses = new List<Course>();
    return;
}

Le code boucle ensuite à travers tous les cours dans la base de données, et vérifie chaque cours par rapport à ceux actuellement affectés au formateur relativement à ceux qui ont été sélectionnés dans la vue. Pour faciliter des recherches efficaces, les deux dernières collections sont stockées dans des objets HashSet.

Si la case pour un cours a été cochée mais que le cours n’est pas dans la propriété de navigation Instructor.Courses, le cours est ajouté à la collection dans la propriété de navigation.

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Add(course);
    }
}

Si la case pour un cours a été cochée mais que le cours est dans la propriété de navigation Instructor.Courses, le cours est supprimé de la propriété de navigation.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.Courses.Remove(course);
    }
}

Dans Views\Instructor\Edit.cshtml, ajoutez un champ Courses avec un tableau de cases à cocher en ajoutant le code suivant immédiatement après les div éléments du OfficeAssignment champ et avant l’élément div du bouton Enregistrer :

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                               name="selectedCourses"
                               value="@course.CourseID"
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                               @course.CourseID @:  @course.Title
                        @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

Une fois que vous avez collé le code, si les sauts de ligne et la mise en retrait ne ressemblent pas à ceux-ci, corrigez manuellement tout ce que vous voyez ici. L’indentation ne doit pas nécessairement être parfaite, mais les lignes @</tr><tr>, @:<td>, @:</td> et @</tr> doivent chacune tenir sur une seule ligne comme dans l’illustration, sinon vous recevrez une erreur d’exécution.

Ce code crée un tableau HTML qui a trois colonnes. Dans chaque colonne se trouve une case à cocher, suivie d’une légende qui est constituée du numéro et du titre du cours. Les cases à cocher ont tous le même nom (« selectedCourses »), qui informe le classeur de modèles qu’ils doivent être traités comme un groupe. L’attribut value de chaque case à cocher est défini sur la valeur de CourseID. Lorsque la page est publiée, le classeur de modèles transmet un tableau au contrôleur qui se compose des valeurs pour seules les CourseID cases à cocher sélectionnées.

Lorsque les cases à cocher sont initialement affichées, celles qui sont destinées aux cours affectés au formateur ont checked des attributs, ce qui les sélectionne (les affiche activées).

Après avoir modifié les devoirs de cours, vous devez être en mesure de vérifier les modifications lorsque le site revient à la Index page. Par conséquent, vous devez ajouter une colonne à la table dans cette page. Dans ce cas, vous n’avez pas besoin d’utiliser l’objet ViewBag , car les informations que vous souhaitez afficher se trouvent déjà dans la Courses propriété de navigation de l’entité Instructor que vous passez à la page en tant que modèle.

Dans Views\Instructor\Index.cshtml, ajoutez un titre Courses immédiatement après le titre Office , comme illustré dans l’exemple suivant :

<tr> 
    <th>Last Name</th> 
    <th>First Name</th> 
    <th>Hire Date</th> 
    <th>Office</th>
    <th>Courses</th>
    <th></th> 
</tr>

Ensuite, ajoutez une nouvelle cellule de détail immédiatement après la cellule de détails de l’emplacement du bureau :

<td>
    @if (item.OfficeAssignment != null)
    {
        @item.OfficeAssignment.Location
    }
</td>
<td>
    @{
        foreach (var course in item.Courses)
        {
            @course.CourseID @:  @course.Title <br />
        }
    }
</td>
<td>
    @Html.ActionLink("Select", "Index", new { id = item.ID }) |
    @Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
    @Html.ActionLink("Details", "Details", new { id = item.ID }) |
    @Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>

Exécutez la page Index de l’instructeur pour afficher les cours attribués à chaque instructeur.

Cliquez sur Modifier sur un instructeur pour afficher la page Modifier.

Modifiez certaines affectations de cours, puis cliquez sur Enregistrer. Les modifications que vous apportez sont reflétées dans la page Index.

Remarque : L’approche adoptée ici pour modifier les données de cours d’instructeur fonctionne bien lorsqu’il existe un nombre limité de cours. Pour les collections qui sont beaucoup plus volumineuses, une autre interface utilisateur et une autre méthode de mise à jour seraient nécessaires.

Mettre à jour DeleteConfirmed

Dans InstructorController.cs, supprimez la DeleteConfirmed méthode et insérez le code suivant à sa place.

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
   Instructor instructor = db.Instructors
     .Include(i => i.OfficeAssignment)
     .Where(i => i.ID == id)
     .Single();

   db.Instructors.Remove(instructor);

    var department = db.Departments
        .Where(d => d.InstructorID == id)
        .SingleOrDefault();
    if (department != null)
    {
        department.InstructorID = null;
    }

   db.SaveChanges();
   return RedirectToAction("Index");
}

Ce code apporte la modification suivante :

  • Si l’instructeur est affecté en tant qu’administrateur d’un service, supprime l’affectation de l’instructeur de ce service. Sans ce code, vous obtenez une erreur d’intégrité référentielle si vous avez essayé de supprimer un instructeur qui a été affecté en tant qu’administrateur pour un service.

Ce code ne gère pas le scénario d’un instructeur affecté en tant qu’administrateur pour plusieurs services. Dans le dernier tutoriel, vous allez ajouter du code qui empêche ce scénario de se produire.

Ajouter des emplacements de bureau et des cours à la page Create

Dans InstructorController.cs, supprimez les HttpGet méthodes, HttpPost Create puis ajoutez le code suivant à leur place :

public ActionResult Create()
{
    var instructor = new Instructor();
    instructor.Courses = new List<Course>();
    PopulateAssignedCourseData(instructor);
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "LastName,FirstMidName,HireDate,OfficeAssignment" )]Instructor instructor, string[] selectedCourses)
{
    if (selectedCourses != null)
    {
        instructor.Courses = new List<Course>();
        foreach (var course in selectedCourses)
        {
            var courseToAdd = db.Courses.Find(int.Parse(course));
            instructor.Courses.Add(courseToAdd);
        }
    }
    if (ModelState.IsValid)
    {
        db.Instructors.Add(instructor);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

Ce code est similaire à ce que vous avez vu pour les méthodes Edit, sauf qu’au départ aucun cours n’est sélectionné. La HttpGet Create méthode appelle la PopulateAssignedCourseData méthode non parce qu’il peut y avoir des cours sélectionnés, mais pour fournir une collection vide pour la boucle dans la foreach vue (sinon, le code de vue lève une exception de référence Null).

La méthode HttpPost Create ajoute chaque cours sélectionné à la propriété de navigation Courses avant le code de modèle qui vérifie les erreurs de validation et ajoute le nouvel instructeur à la base de données. Les cours sont ajoutés même s’il existe des erreurs de modèle afin que, lorsqu’il existe des erreurs de modèle (par exemple, l’utilisateur a clé une date non valide) afin que lorsque la page est réiniturée avec un message d’erreur, toutes les sélections de cours effectuées sont automatiquement restaurées.

Notez que pour pouvoir ajouter des cours à la propriété de navigation Courses, vous devez initialiser la propriété en tant que collection vide :

instructor.Courses = new List<Course>();

Comme alternative à cette opération dans le code du contrôleur, vous pouvez l’effectuer dans le modèle Instructor en modifiant le getter de propriété pour créer automatiquement la collection si elle n’existe pas, comme le montre l’exemple suivant :

private ICollection<Course> _courses;
public virtual ICollection<Course> Courses 
{ 
    get
    {
        return _courses ?? (_courses = new List<Course>());
    }
    set
    {
        _courses = value;
    } 
}

Si vous modifiez la propriété Courses de cette façon, vous pouvez supprimer le code d’initialisation explicite de la propriété dans le contrôleur.

Dans Views\Instructor\Create.cshtml, ajoutez une zone de texte d’emplacement de bureau et des cases à cocher de cours après le champ date d’embauche et avant le bouton Envoyer .

<div class="form-group">
    @Html.LabelFor(model => model.OfficeAssignment.Location, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.OfficeAssignment.Location)
        @Html.ValidationMessageFor(model => model.OfficeAssignment.Location)
    </div>
</div>

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                               name="selectedCourses"
                               value="@course.CourseID"
                               @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                               @course.CourseID @:  @course.Title
                        @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

Après avoir collé le code, corrigez les sauts de ligne et la mise en retrait comme vous l’avez fait précédemment pour la page Modifier.

Exécutez la page Créer et ajoutez un instructeur.

Gérer des transactions

Comme expliqué dans le didacticiel de fonctionnalité CRUD de base, par défaut, Entity Framework implémente implicitement les transactions. Pour les scénarios où vous avez besoin d’un contrôle supplémentaire ( par exemple, si vous souhaitez inclure des opérations effectuées en dehors d’Entity Framework dans une transaction), consultez Utilisation des transactions sur MSDN.

Obtenir le code

Télécharger le projet terminé

Ressources supplémentaires

Vous trouverez des liens vers d’autres ressources Entity Framework dans ASP.NET Accès aux données - Ressources recommandées.

Étape suivante

Dans ce tutoriel, vous allez :

  • Pages de cours personnalisées
  • Page Ajout d’office aux instructeurs
  • Ajout de cours à la page des instructeurs
  • Mise à jour de DeleteConfirmed
  • Ajout de l’emplacement du bureau et des cours à la page Créer

Passez à l’article suivant pour apprendre à implémenter un modèle de programmation asynchrone.