Tutorial: Aktualisieren verwandter Daten mit EF in einer ASP.NET MVC-App

Im vorherigen Tutorial haben Sie verwandte Daten angezeigt. In diesem Tutorial aktualisieren Sie verwandte Daten. Bei den meisten Beziehungen kann dies durch Aktualisieren von Fremdschlüsselfeldern oder Navigationseigenschaften erfolgen. Bei m:n-Beziehungen macht Entity Framework die Jointabelle nicht direkt verfügbar, sodass Sie den entsprechenden Navigationseigenschaften Entitäten hinzufügen und daraus entfernen.

In den folgenden Abbildungen werden die Seiten dargestellt, mit denen Sie arbeiten werden.

Course_create_page

Instructor_edit_page_with_courses

Dozentenbearbeitung mit Kursen

In diesem Tutorial:

  • Anpassen von Kursseiten
  • Seite "Office zu Kursleitern hinzufügen"
  • Seite "Kurse zu Kursleitern hinzufügen"
  • Aktualisieren von DeleteConfirmed
  • Hinzufügen von einem Bürostandort und von Kursen zu der Seite „Erstellen“

Voraussetzungen

Anpassen von Kursseiten

Wenn eine neue Kursentität erstellt wird, muss diese in Beziehung zu einer vorhandenen Abteilung stehen. Um dies zu vereinfachen, enthält der Gerüstcode Controllermethoden und Ansichten zum „Erstellen“ und „Bearbeiten“, die eine Dropdownliste enthalten, aus denen der Fachbereich ausgewählt werden kann. Die Dropdownliste legt die Fremdschlüsseleigenschaft Course.DepartmentID fest. Mehr benötigt Entity Framework nicht, um die Navigationseigenschaft Department mit der passenden Department-Entität zu laden. Verwenden Sie den Gerüstcode, aber nehmen Sie kleine Änderungen vor, um die Fehlerbehandlung hinzuzufügen und die Dropdownliste zu sortieren.

Löschen Sie in CourseController.cs die vier Create Methoden und , Edit und ersetzen Sie sie durch den folgenden Code:

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);
} 

Fügen Sie am Anfang der Datei die folgende using Anweisung hinzu:

using System.Data.Entity.Infrastructure;

Die PopulateDepartmentsDropDownList -Methode ruft eine Liste aller Abteilungen ab, die nach Namen sortiert sind, erstellt eine SelectList Auflistung für eine Dropdownliste und übergibt die Auflistung an die Ansicht in einer ViewBag Eigenschaft. Die Methode akzeptiert den optionalen selectedDepartment-Parameter, über den der Code das Element angeben kann, das ausgewählt wird, wenn die Dropdownliste gerendert wird. Die Ansicht übergibt den Namen DepartmentID an das DropDownList-Hilfsprogramm , und das Hilfsprogramm weiß dann, im ViewBag -Objekt nach einem SelectList namens DepartmentIDzu suchen.

Die HttpGetCreate -Methode ruft die PopulateDepartmentsDropDownList -Methode auf, ohne das ausgewählte Element festzulegen, da für einen neuen Kurs die Abteilung noch nicht eingerichtet wurde:

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

Die HttpGetEdit -Methode legt das ausgewählte Element basierend auf der ID der Abteilung fest, die dem zu bearbeitenden Kurs bereits zugewiesen ist:

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);
}

Die HttpPost Methoden für und CreateEdit enthalten auch Code, der das ausgewählte Element festlegt, wenn die Seite nach einem Fehler erneut angezeigt wird:

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);

Dieser Code stellt sicher, dass die ausgewählte Abteilung ausgewählt bleibt, wenn die Seite erneut angezeigt wird, um die Fehlermeldung anzuzeigen.

Die Kursansichten sind bereits mit Dropdownlisten für das Abteilungsfeld gerüstet, aber Sie möchten nicht, dass die Abteilungs-ID für dieses Feld Untertitel. Nehmen Sie daher die folgende hervorgehobene Änderung an der Datei Views\Course\Create.cshtml vor, um die Untertitel zu ändern.

@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")
}

Nehmen Sie die gleiche Änderung in Views\Course\Edit.cshtml vor.

Normalerweise erstellt das Gerüst einen Primärschlüssel nicht, da der Schlüsselwert von der Datenbank generiert wird und nicht geändert werden kann und kein aussagekräftiger Wert ist, der Benutzern angezeigt werden soll. Für Kursentitäten enthält der Gerüstbau ein Textfeld für das CourseID Feld, da es versteht, dass das DatabaseGeneratedOption.None Attribut bedeutet, dass der Benutzer den Primärschlüsselwert eingeben kann. Es versteht jedoch nicht, dass sie in den anderen Ansichten angezeigt werden soll, da die Zahl aussagekräftig ist, sodass Sie sie manuell hinzufügen müssen.

Fügen Sie in Views\Course\Edit.cshtml vor dem Feld Titel ein Feld für die Kursnummer hinzu. Da es sich um den Primärschlüssel handelt, wird er angezeigt, kann aber nicht geändert werden.

<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>

Es gibt bereits ein ausgeblendetes Feld (Html.HiddenFor Hilfsprogramm) für die Kursnummer in der Bearbeitungsansicht. Wenn Sie ein Html.LabelFor-Hilfsprogramm hinzufügen, ist das ausgeblendete Feld nicht erforderlich, da die Kursnummer nicht in die bereitgestellten Daten aufgenommen wird, wenn der Benutzer auf der Seite Bearbeiten auf Speichern klickt.

Ändern Sie in Views\Course\Delete.cshtml und Views\Course\Details.cshtml den Abteilungsnamen Untertitel von "Name" in "Abteilung", und fügen Sie vor dem Feld Titel ein Feld für die Kursnummer hinzu.

<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>

Führen Sie die Seite Erstellen aus (zeigen Sie die Seite Kursindex an, und klicken Sie auf Neu erstellen), und geben Sie Daten für einen neuen Kurs ein:

Wert Einstellung
Number Geben Sie 1000 ein.
Titel Geben Sie Algebra ein.
Mitwirkende Geben Sie 4 ein.
Department Wählen Sie Mathematik aus.

Klicken Sie auf Erstellen. Die Seite "Kursindex" wird mit dem neuen Kurs angezeigt, der der Liste hinzugefügt wurde. Der Fachbereichsname in der Indexseitenliste wurde der Navigationseigenschaft entnommen und deutet darauf hin, dass die Beziehung ordnungsgemäß festgelegt wurde.

Führen Sie die Seite Bearbeiten aus (zeigen Sie die Seite Kursindex an, und klicken Sie in einem Kurs auf Bearbeiten ).

Ändern Sie die Daten auf der Seite, und klicken Sie auf Speichern. Die Seite "Kursindex" wird mit den aktualisierten Kursdaten angezeigt.

Seite "Office zu Kursleitern hinzufügen"

Bei der Bearbeitung eines Dozentendatensatzes sollten Sie auch die Bürozuweisung des Dozenten aktualisieren. Die Instructor Entität hat eine 1:0-Beziehung oder eine Beziehung mit der OfficeAssignment Entität, was bedeutet, dass Sie die folgenden Situationen behandeln müssen:

  • Wenn der Benutzer die Bürozuweisung löscht und sie ursprünglich einen Wert hatte, müssen Sie die OfficeAssignment Entität entfernen und löschen.
  • Wenn der Benutzer einen Bürozuweisungswert eingibt und dieser ursprünglich leer war, müssen Sie eine neue OfficeAssignment Entität erstellen.
  • Wenn der Benutzer den Wert einer Bürozuweisung ändert, müssen Sie den Wert in einer vorhandenen OfficeAssignment Entität ändern.

Öffnen Sie InstructorController.cs , und sehen Sie sich die Methode an HttpGetEdit :

{
    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);
}

Der gerüstete Code hier ist nicht das, was Sie möchten. Es werden Daten für eine Dropdownliste eingerichtet, aber Sie benötigen ein Textfeld. Ersetzen Sie diese Methode durch den folgenden Code:

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);
}

Dieser Code löscht die ViewBag -Anweisung und fügt eager loading für die zugeordnete OfficeAssignment Entität hinzu. Sie können keine eifrigen Ladevorgänge mit der Find -Methode ausführen, sodass stattdessen die Where Methoden und Single verwendet werden, um den Kursleiter auszuwählen.

Ersetzen Sie die HttpPostEdit -Methode durch den folgenden Code. die Aktualisierungen von Office-Zuweisungen behandeln:

[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);
}

Der Verweis auf RetryLimitExceededException erfordert eine using -Anweisung. Zum Hinzufügen zeigen Sie mit dem Mauszeiger auf RetryLimitExceededException. Die folgende Meldung wird angezeigt:  Ausnahmemeldung wiederholen

Wählen Sie Potenzielle Fehlerbehebungen anzeigen aus, und verwenden Sie dann System.Data.Entity.Infrastructure.

Beheben einer Wiederholungs-Ausnahme

Der Code führt Folgendes aus:

  • Ändert den Methodennamen in EditPost , da die Signatur jetzt mit der HttpGet -Methode identisch ist (das ActionName Attribut gibt an, dass die URL /Edit/ weiterhin verwendet wird).

  • Ruft die aktuelle Entität Instructor von der Datenbank über Eager Loading für die Navigationseigenschaft OfficeAssignment ab. Dies entspricht dem, was Sie in der HttpGetEdit -Methode ausgeführt haben.

  • Aktualisiert die abgerufene Entität Instructor mit Werten aus der Modellbindung. Mit der verwendeten TryUpdateModel-Überladung können Sie die Eigenschaften auflisten, die Sie einschließen möchten. Dadurch wird eine Überbuchung verhindert, wie im zweiten Tutorial erläutert.

    if (TryUpdateModel(instructorToUpdate, "",
          new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
    
  • Wenn kein Standort für das Büro angegeben wird, wird die Instructor.OfficeAssignment-Eigenschaft auf NULL festgelegt, sodass die zugehörige Zeile aus der OfficeAssignment-Tabelle gelöscht wird.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Speichert die Änderungen in der Datenbank.

Fügen Sie in Views\Instructor\Edit.cshtml nach den div Elementen für das Feld Einstellungsdatum ein neues Feld zum Bearbeiten des Bürostandorts hinzu:

<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>

Führen Sie die Seite aus (wählen Sie die Registerkarte Dozenten aus, und klicken Sie dann auf Bearbeiten für einen Dozenten). Ändern Sie den Standort des Büros, und klicken Sie auf Speichern.

Seite "Kurse zu Kursleitern hinzufügen"

Dozenten können eine beliebige Anzahl von Kursen unterrichten. Jetzt erweitern Sie die Seite "Ausbilder bearbeiten", indem Sie die Möglichkeit hinzufügen, Kursaufgaben mithilfe einer Gruppe von Kontrollkästchen zu ändern.

Die Beziehung zwischen den Course Entitäten und Instructor ist m:n, was bedeutet, dass Sie keinen direkten Zugriff auf die Fremdschlüsseleigenschaften haben, die sich in der Jointabelle befinden. Stattdessen fügen Sie der Navigationseigenschaft Entitäten hinzu und entfernen sie Instructor.Courses daraus.

Die Benutzeroberfläche, über die Sie ändern können, welchen Kursen ein Dozent zugewiesen ist, besteht aus einer Reihe von Kontrollkästchen. Für jeden Kurs in der Datenbank wird ein Kontrollkästchen angezeigt. Die Kontrollkästchen, denen der Dozent zu diesem Zeitpunkt zugewiesen ist, sind aktiviert. Der Benutzer kann Kontrollkästchen aktivieren oder deaktivieren, um Kurszuweisungen zu ändern. Wenn die Anzahl der Kurse viel größer wäre, würden Sie wahrscheinlich eine andere Methode zum Darstellen der Daten in der Ansicht verwenden, aber Sie würden die gleiche Methode zum Bearbeiten von Navigationseigenschaften verwenden, um Beziehungen zu erstellen oder zu löschen.

Verwenden Sie eine Ansichtsmodellklasse, um Daten für die Ansicht bereitzustellen, um eine Liste mit Kontrollkästchen zu erstellen. Erstellen Sie AssignedCourseData.cs im Ordner ViewModels , und ersetzen Sie den vorhandenen Code durch den folgenden Code:

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

Ersetzen Sie in InstructorController.cs die HttpGetEdit -Methode durch den folgenden Code. Die Änderungen werden hervorgehoben.

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;
}

Über den Code wird für die Courses-Navigationseigenschaft Eager Loading hinzugefügt und die neue PopulateAssignedCourseData-Methode aufgerufen, um über die Ansichtsmodellklasse AssignedCourseData Informationen für das Kontrollkästchenarray zur Verfügung zu stellen.

Der Code in der PopulateAssignedCourseData-Methode liest alle Course-Entitäten, um mithilfe der Ansichtsmodellklasse eine Liste der Kurse zu laden. Für jeden Kurs überprüft der Code, ob dieser in der Courses-Navigationseigenschaft des Dozenten vorhanden ist. Um eine effiziente Suche bei der Überprüfung zu erstellen, ob dem Kursleiter ein Kurs zugewiesen ist, werden die dem Kursleiter zugewiesenen Kurse in eine HashSet-Sammlung eingefügt. Die Assigned -Eigenschaft ist für Kurse festgelegt, true die dem Kursleiter zugewiesen sind. Die Ansicht verwendet diese Eigenschaft, um zu bestimmen, welche Kontrollkästchen als aktiviert angezeigt werden sollen. Schließlich wird die Liste an die Ansicht in einer ViewBag Eigenschaft übergeben.

Fügen Sie als nächstes den Code hinzu, der ausgeführt wird, wenn der Benutzer auf Speichern klickt. Ersetzen Sie die EditPost -Methode durch den folgenden Code, der eine neue Methode aufruft, die die Courses Navigationseigenschaft der Instructor Entität aktualisiert. Die Änderungen werden hervorgehoben.

[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);
         }
      }
   }
}

Die Methodensignatur unterscheidet sich jetzt von der HttpGetEdit -Methode. Daher ändert sich der Methodenname von EditPost zurück in Edit.

Da die Ansicht keine Auflistung von Course Entitäten enthält, kann der Modellbinder die Courses Navigationseigenschaft nicht automatisch aktualisieren. Anstatt die Modellbinder zu verwenden, um die Courses Navigationseigenschaft zu aktualisieren, tun Sie dies in der neuen UpdateInstructorCourses Methode. Aus diesem Grund müssen Sie die Courses-Eigenschaft von der Modellbindung ausschließen. Dies erfordert keine Änderung am Code, der TryUpdateModel aufruft, da Sie die explizite Listenüberladung verwenden und Courses nicht in der Include-Liste enthalten ist.

Wenn keine Kontrollkästchen aktiviert wurden, initialisiert der Code in UpdateInstructorCourses die Courses Navigationseigenschaft eine leere Auflistung:

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

Der Code führt dann eine Schleife durch alle Kurse in der Datenbank aus und überprüft jeden Kurs im Hinblick auf die Kurse, die zu diesem Zeitpunkt dem Dozenten zugewiesen sind, und denen, die in der Ansicht aktiviert wurden. Die beiden letzten Auflistungen werden in HashSet-Objekten gespeichert, um Suchvorgänge effizienter zu gestalten.

Wenn das Kontrollkästchen für einen Kurs aktiviert ist, dieser Kurs jedoch nicht in der Instructor.Courses-Navigationseigenschaft vorhanden ist, wird dieser der Auflistung in der Navigationseigenschaft hinzugefügt.

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

Wenn das Kontrollkästchen für einen Kurs aktiviert ist, dieser Kurs jedoch nicht in der Instructor.Courses-Navigationseigenschaft vorhanden ist, wird dieser aus der Auflistung in der Navigationseigenschaft gelöscht.

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

Fügen Sie in Views\Instructor\Edit.cshtml ein Kursfeld mit einem Array von Kontrollkästchen hinzu, indem Sie direkt nach den Elementen für das divOfficeAssignment Feld und vor dem Element für die Schaltfläche Speichern den div folgenden Code hinzufügen:

<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>

Wenn Zeilenumbrüche und Einzug nach dem Einfügen des Codes nicht wie hier aussehen, korrigieren Sie alles manuell, damit es wie hier aussieht. Der Einzug muss nicht perfekt sein, die Zeilen @</tr><tr>, @:<td>, @:</td> und @</tr> müssen jedoch, wie dargestellt, jeweils in einer einzelnen Zeile stehen. Ansonsten wird ein Laufzeitfehler ausgelöst.

Dieser Code erstellt eine HTML-Tabelle mit drei Spalten. Jede Spalte enthält ein Kontrollkästchen gefolgt von einem Titel, der aus der Kursnummer und dem Kurstitel besteht. Die Kontrollkästchen haben alle denselben Namen ("selectedCourses"), wodurch der Modellbinder darüber informiert wird, dass sie als Gruppe behandelt werden sollen. Das value Attribut jedes Kontrollkästchens ist auf den Wert von CourseID. Festgelegt, wenn die Seite veröffentlicht wird, übergibt der Modellbinder ein Array an den Controller, das nur aus den CourseID Werten für die aktivierten Kontrollkästchen besteht.

Wenn die Kontrollkästchen zunächst gerendert werden, verfügen checked die Kontrollkästchen für Kurse, die dem Kursleiter zugewiesen sind, über Attribute, die sie auswählen (zeigt sie aktiviert an).

Nachdem Sie Kurszuweisungen geändert haben, sollten Sie die Änderungen überprüfen können, wenn die Website zur Index Seite zurückkehrt. Daher müssen Sie der Tabelle auf dieser Seite eine Spalte hinzufügen. In diesem Fall müssen Sie das ViewBag -Objekt nicht verwenden, da sich die anzuzeigenden Informationen bereits in der Courses Navigationseigenschaft der Entität befinden, die Instructor Sie als Modell an die Seite übergeben.

Fügen Sie in Views\Instructor\Index.cshtml eine Kursüberschrift hinzu, die unmittelbar der Office-Überschrift folgt, wie im folgenden Beispiel gezeigt:

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

Fügen Sie dann direkt nach der Detailzelle des Bürostandorts eine neue Detailzelle hinzu:

<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>

Führen Sie die Seite Instructor Index aus, um die Kurse anzuzeigen, die den einzelnen Kursleitern zugewiesen sind.

Klicken Sie in einem Kursleiter auf Bearbeiten , um die Seite Bearbeiten anzuzeigen.

Ändern Sie einige Kurszuweisungen, und klicken Sie auf Speichern. Die Änderungen werden auf der Indexseite angezeigt.

Hinweis: Der hier verfolgte Ansatz zum Bearbeiten von Kursdaten funktioniert gut, wenn eine begrenzte Anzahl von Kursen vorhanden ist. Bei umfangreicheren Auflistungen wären eine andere Benutzeroberfläche und eine andere Aktualisierungsmethode erforderlich.

Update DeleteConfirmed

Löschen Sie in InstructorController.cs die DeleteConfirmed -Methode, und fügen Sie an ihrer Stelle den folgenden Code ein.

[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");
}

Dieser Code nimmt die folgende Änderung vor:

  • Wenn der Kursleiter als Administrator einer Abteilung zugewiesen ist, entfernt die Dozentenzuweisung aus dieser Abteilung. Ohne diesen Code erhalten Sie einen Fehler bei der referenziellen Integrität, wenn Sie versuchen, einen Kursleiter zu löschen, der als Administrator für eine Abteilung zugewiesen wurde.

Dieser Code behandelt nicht das Szenario eines Kursleiters, der als Administrator für mehrere Abteilungen zugewiesen ist. Im letzten Tutorial fügen Sie Code hinzu, der verhindert, dass dieses Szenario passiert.

Hinzufügen von einem Bürostandort und von Kursen zu der Seite „Erstellen“

Löschen Sie in InstructorController.cs die HttpGet Methoden und HttpPostCreate , und fügen Sie dann den folgenden Code an deren Stelle hinzu:

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);
}

Dieser Code ähnelt dem, was Sie für die Bearbeitungsmethoden gesehen haben, mit dem Unterschied, dass zunächst keine Kurse ausgewählt sind. Die HttpGetCreate -Methode ruft die PopulateAssignedCourseData -Methode nicht auf, weil möglicherweise Kurse ausgewählt sind, sondern, um eine leere Auflistung für die foreach Schleife in der Ansicht bereitzustellen (andernfalls würde der Ansichtscode eine NULL-Verweis-Ausnahme auslösen).

Die HttpPost Create-Methode fügt jeden ausgewählten Kurs der Kursnavigationseigenschaft vor dem Vorlagencode hinzu, der nach Validierungsfehlern sucht, und fügt den neuen Kursleiter der Datenbank hinzu. Kurse werden auch dann hinzugefügt, wenn Modellfehler vorhanden sind, sodass beim Auftreten von Modellfehlern (z. B. beim Festlegen eines ungültigen Datums durch den Benutzer) automatisch alle Kursauswahlen wiederhergestellt werden, wenn die Seite mit einer Fehlermeldung erneut angezeigt wird.

Beachten Sie, dass Sie die Courses-Navigationseigenschaft als leere Auflistung initialisieren müssen, wenn Sie dieser Kurse hinzufügen möchten:

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

Wenn Sie dies nicht im Controllercode durchführen möchten, können Sie dies auch im Dozentenmodell tun, indem Sie den Eigenschaftengetter ändern, um falls nötig automatisch die Auflistung zu erstellen. Dies wird im folgenden Code dargestellt:

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

Wenn Sie die Courses-Eigenschaft auf diese Weise ändern, können Sie den expliziten Code zum Initialisieren der Eigenschaft aus dem Controller entfernen.

Fügen Sie in Views\Instructor\Create.cshtml ein Textfeld für den Bürostandort und die Kontrollkästchen "Kurs" nach dem Feld "Mietdatum" und vor der Schaltfläche "Senden " hinzu.

<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>

Nachdem Sie den Code eingefügt haben, korrigieren Sie Zeilenumbrüche und Einzug wie zuvor für die Seite Bearbeiten.

Führen Sie die Seite Erstellen aus, und fügen Sie einen Kursleiter hinzu.

Behandeln von Transaktionen

Wie im Tutorial grundlegende CRUD-Funktionalität erläutert, implementiert Entity Framework standardmäßig implizit Transaktionen. Szenarien, in denen Sie mehr Kontrolle benötigen , z. B. wenn Sie Vorgänge außerhalb von Entity Framework in eine Transaktion einbeziehen möchten, finden Sie unter Arbeiten mit Transaktionen auf MSDN.

Abrufen des Codes

Herunterladen des abgeschlossenen Projekts

Zusätzliche Ressourcen

Links zu anderen Entity Framework-Ressourcen finden Sie unter ASP.NET Datenzugriff – Empfohlene Ressourcen.

Nächster Schritt

In diesem Tutorial:

  • Angepasste Kursseiten
  • Seite "Office zu Kursleitern" hinzugefügt
  • Seite "Kurse zu Kursleitern" hinzugefügt
  • DeleteConfirmed wurde aktualisiert.
  • Office-Standort und -Kurse zur Seite "Erstellen" hinzugefügt

Fahren Sie mit dem nächsten Artikel fort, um zu erfahren, wie Sie ein asynchrones Programmiermodell implementieren.