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

Dans le tutoriel précédent, vous avez affiché des 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 ajoutez et supprimez donc 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 tutoriel, vous allez :

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

Prérequis

Personnaliser les pages de 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 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 transmet le nom DepartmentID à l’assistance DropDownList , et l’assistance sait alors rechercher dans l’objet ViewBag un SelectList nommé DepartmentID.

La HttpGetCreate 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 HttpGetEdit méthode définit l’élément sélectionné, en fonction de l’ID du service qui est 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 avec des listes déroulantes pour le champ department, mais vous ne souhaitez pas que le DepartmentID légende pour ce champ. Par conséquent, apportez la modification en surbrillance suivante au fichier Views\Course\Create.cshtml pour modifier le 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 crée pas de structure de clé primaire, car la valeur de 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, la structure inclut une zone de texte pour le CourseID champ, car il comprend que l’attribut DatabaseGeneratedOption.None signifie que l’utilisateur doit être en mesure d’entrer la valeur de la clé primaire. Mais il ne comprend pas que, étant donné que le nombre est significatif, 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 . Étant donné qu’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 helper) pour le numéro de cours dans la vue Modifier. L’ajout d’une assistance Html.LabelFor n’élimine pas la nécessité d’utiliser le champ masqué, car cela 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 le nom du service légende de « Name » par « Department » et ajoutez un champ de 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 du cours et cliquez sur Créer) et entrez les données d’un nouveau cours :

Valeur Paramètre
Nombre Entrez 1000.
Titre Entrez Algèbre.
Crédits Entrez 4.
department Sélectionnez Mathématiques.

Cliquez sur Créer. La page Index du 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 du cours s’affiche avec les données de cours mises à jour.

Page Ajouter office 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’affectation de bureau et qu’il avait initialement une valeur, vous devez supprimer et supprimer l’entité OfficeAssignment .
  • Si l’utilisateur entre une valeur d’affectation de bureau et qu’elle était vide à l’origine, vous devez créer une OfficeAssignment entité.
  • 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 HttpGetEdit 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é 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 hâtif pour l’entité associée OfficeAssignment . Vous ne pouvez pas effectuer de chargement hâtif avec la Find méthode . Les méthodes et Single sont donc utilisées à la Where place pour sélectionner l’instructeur.

Remplacez la HttpPostEdit méthode par le code suivant. qui gère les mises à jour des affectations office :

[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 nécessite une using instruction ; pour l’ajouter , pointez 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 l’exception de nouvelle tentative

Le code effectue les actions suivantes :

  • Remplace le nom de la méthode par , car la signature est désormais identique à EditPost 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. Cela est identique à ce que vous avez fait dans la HttpGetEdit 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. Cela empêche la sur-publication, comme expliqué dans le deuxième tutoriel.

    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 Date d’embauche , 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.

Page Ajouter des cours aux 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 affectations de cours à l’aide d’un groupe de zones case activée.

La relation entre les Course entités et Instructor est 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 dans 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 élevé, vous voudriez probablement utiliser une méthode différente pour présenter les 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 HttpGetEdit 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 à l’instructeur, les cours attribués à l’instructeur sont placés dans une collection HashSet . La Assigned propriété est définie sur 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 à la vue 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 étant désormais différente de la HttpGetEdit méthode , 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 automatiquement à jour la propriété de Courses 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 ne figure pas dans la liste include.

Si aucune zone case activée n’a été sélectionnée, le code dans UpdateInstructorCourses initialise 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 zones case activée en ajoutant le code suivant immédiatement après les div éléments du OfficeAssignment champ et avant l’élément div pour le 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>

Après avoir collé le code, si les sauts de ligne et la mise en retrait ne ressemblent pas à ce qu’ils font ici, corrigez manuellement tout de sorte qu’il ressemble à 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 zones case activée ont toutes le même nom (« selectedCourses »), ce qui indique au classeur de modèles qu’elles doivent être traitées comme un groupe. L’attribut value de chaque zone de case activée est défini sur la valeur Lors de la publication de CourseID. la page, le classeur de modèles transmet un tableau au contrôleur qui se compose des CourseID valeurs pour les case activée zones sélectionnées uniquement.

Lorsque les zones de case activée sont initialement affichées, celles qui sont destinées aux cours affectés à l’instructeur ont checked des attributs, qui les sélectionnent (les affichent activées).

Après avoir modifié les affectations de cours, vous souhaiterez pouvoir 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 transmettez à la page en tant que modèle.

Dans Views\Instructor\Index.cshtml, ajoutez un en-tête 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>

Ajoutez ensuite 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 voir les cours attribués à chaque instructeur.

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

Modifiez certaines affectations de cours et 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 des cours de l’instructeur fonctionne bien lorsqu’il y a 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 les modifications suivantes :

  • 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 obtiendriez une erreur d’intégrité référentielle si vous essayiez 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 et HttpPostCreate , 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 HttpGetCreate méthode appelle la PopulateAssignedCourseData méthode non pas parce qu’il peut y avoir des cours sélectionnés, mais afin de fournir une collection vide pour la foreach boucle dans la vue (sinon, le code de la 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 recherche 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, de sorte que lorsqu’il y a des erreurs de modèle (par exemple, l’utilisateur a clé une date non valide) de sorte que lorsque la page est réaffiché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 zones de case activée 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 Sur les fonctionnalités CRUD de base, entity Framework implémente implicitement les transactions par défaut. Pour les scénarios dans lesquels vous avez besoin de davantage de contrôle, 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 découvrir comment implémenter un modèle de programmation asynchrone.