Freigeben über


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

Im vorherigen Lernprogramm haben Sie verwandte Daten angezeigt. In diesem Lernprogramm 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 Verknüpfungstabelle nicht direkt verfügbar, sodass Sie Entitäten zu und aus den entsprechenden Navigationseigenschaften 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

Kursleiter bearbeiten mit Kursen

In diesem Tutorial:

  • Anpassen von Kursseiten
  • Hinzufügen von Office zur Kursleiterseite
  • Hinzufügen von Kursen zur Kursleiterseite
  • DeleteConfirmed aktualisieren
  • 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 MethodenEdit, 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 die folgende using Anweisung am Anfang der Datei hinzu:

using System.Data.Entity.Infrastructure;

Die PopulateDepartmentsDropDownList Methode ruft eine Liste aller Abteilungen nach Namen ab, 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 der Hilfsprogramm weiß dann, in dem ViewBag Objekt nach einem SelectList benannten DepartmentIDObjekt zu suchen.

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

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

Die HttpGet Edit Methode legt das ausgewählte Element basierend auf der ID der Abteilung fest, die bereits dem bearbeiteten Kurs 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 beide und Edit Create auch Code, der das ausgewählte Element festlegt, wenn sie die Seite nach einem Fehler erneut anzeigen:

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 beim erneuten Anzeigen der Seite, um die Fehlermeldung anzuzeigen, die ausgewählte Abteilung ausgewählt bleibt.

Die Kursansichten sind bereits mit Dropdownlisten für das Abteilungsfeld gerüstet, aber Sie möchten die Abteilungs-ID-Beschriftung für dieses Feld nicht. Nehmen Sie daher die folgende hervorgehobene Änderung an der Datei "Views\Course\Create.cshtml " vor, um die Beschriftung 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 dieselbe Änderung in "Views\Course\Edit.cshtml" vor.

Normalerweise erstellt das Gerüst kein Gerüst für einen Primärschlüssel, da der Schlüsselwert von der Datenbank generiert wird und nicht geändert werden kann und kein sinnvoller Wert ist, der benutzern angezeigt werden kann. Für Kursentitäten enthält das Gerüst 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. Aber es versteht nicht, dass die Zahl sinnvoll ist, die Sie in den anderen Ansichten sehen möchten, daher müssen Sie sie manuell hinzufügen.

Fügen Sie in Views\Course\Edit.cshtml ein Kursnummernfeld vor dem Feld "Titel " hinzu. Da er der Primärschlüssel ist, 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 Hilfsfeld) für die Kursnummer in der Bearbeitungsansicht. Durch das Hinzufügen eines Html.LabelFor-Hilfsfelds wird die Notwendigkeit des ausgeblendeten Felds nicht beseitigt, da die Kursnummer nicht in die geposteten 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 die Bezeichnung des Abteilungsnamens von "Name" in "Abteilung", und fügen Sie vor dem Feld "Titel" ein Kursnummernfeld 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
Anzahl Geben Sie 1000 ein.
Titel Geben Sie Algebra ein.
Gutschriften 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 auf "Auf einem Kurs bearbeiten ").

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

Hinzufügen von Office zur Kursleiterseite

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

  • Wenn der Benutzer die Office-Zuweisung löscht und ursprünglich einen Wert hatte, müssen Sie die OfficeAssignment Entität entfernen und löschen.
  • Wenn der Benutzer einen Office-Zuordnungswert eingibt und 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 HttpGet Edit Methode an:

{
    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 hier enthaltene Gerüstcode ist nicht das, was Sie möchten. Sie richten Daten für eine Dropdownliste ein, 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 legt die ViewBag Anweisung ab und fügt eifrig das Laden für die zugeordnete OfficeAssignment Entität hinzu. Sie können keine eifrigen Ladevorgänge mit der Find Methode ausführen, daher werden stattdessen die Where Methoden und Single Methoden verwendet, um den Kursleiter auszuwählen.

Ersetzen Sie die HttpPost Edit Methode durch den folgenden Code. die Office-Zuweisungsaktualisierungen behandelt:

[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 erfordert RetryLimitExceededException eine using Anweisung; um sie hinzuzufügen – zeigen Sie mit dem Mauszeiger.RetryLimitExceededException Die folgende Meldung wird angezeigt:  Ausnahmemeldung erneut versuchen

Wählen Sie " Potenzielle Fixes anzeigen" und dann "System.Data.Entity.Infrastructure" aus.

Retry-Ausnahme auflösen

Der Code führt Folgendes aus:

  • Ändert den Methodennamen so EditPost , dass die Signatur jetzt mit der HttpGet Methode identisch ist (das ActionName Attribut gibt an, dass die /Edit/URL 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 HttpGet Edit Methode getan 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, wie im zweiten Tutorial beschrieben, vermieden, dass zu viele Angaben gemacht werden.

    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 "Kursleiter" aus, und klicken Sie dann auf "Instruktor bearbeiten "). Ändern Sie den Standort des Büros, und klicken Sie auf Speichern.

Hinzufügen von Kursen zur Kursleiterseite

Dozenten können eine beliebige Anzahl von Kursen unterrichten. Jetzt verbessern Sie die Seite "Kursleiterbearbeitung", indem Sie die Möglichkeit zum Ändern von Kursaufgaben mithilfe einer Gruppe von Kontrollkästchen hinzufügen.

Die Beziehung zwischen und Entitäten Course Instructor ist n:n, was bedeutet, dass Sie keinen direkten Zugriff auf die Fremdschlüsseleigenschaften haben, die sich in der Verknüpfungstabelle befinden. Stattdessen fügen Sie Entitäten zu und aus der Instructor.Courses Navigationseigenschaft hinzu und entfernen diese.

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 dieselbe 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 HttpGet Edit 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 zu erstellen, wenn geprüft wird, 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 , denen der Kursleiter zugewiesen ist. 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 HttpGet Edit Methode, sodass sich der Methodenname von EditPost hinten in Edit.

Da die Ansicht nicht über eine Sammlung von Course Entitäten verfügt, kann der Modellordner die Courses Navigationseigenschaft nicht automatisch aktualisieren. Anstatt die Modellmappe zum Aktualisieren der Courses Navigationseigenschaft zu verwenden, führen Sie dies in der neuen UpdateInstructorCourses Methode aus. Aus diesem Grund müssen Sie die Courses-Eigenschaft von der Modellbindung ausschließen. Dies erfordert keine Änderung des Codes, der TryUpdateModel aufruft, da Sie die explizite Listenüberladung verwenden und Courses nicht in der Includeliste enthalten sind.

Wenn keine Kontrollkästchen aktiviert wurden, initialisiert der Code UpdateInstructorCourses die Courses Navigationseigenschaft mit einer leeren 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 Feld "Kurse " mit einem Array von Kontrollkästchen hinzu, indem Sie den folgenden Code unmittelbar hinter den div Elementen für das OfficeAssignment Feld und vor dem div Element für die Schaltfläche "Speichern " 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>

Nachdem Sie den Code eingefügt haben, wenn Zeilenumbrüche und Einzug 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 Modellordner informiert wird, dass sie als Gruppe behandelt werden sollen. Das value Attribut jedes Kontrollkästchens wird auf den Wert der CourseID. Seite festgelegt, wenn die Seite gepostet wird, übergibt der Modellordner ein Array an den Controller, der nur aus den CourseID Werten für die ausgewählten Kontrollkästchen besteht.

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

Nachdem Sie Kursaufgaben 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 Instructor Entität befinden, die Sie als Modell an die Seite übergeben.

Fügen Sie in Views\Instructor\Index.cshtml eine Kursüberschrift unmittelbar nach der Office-Überschrift hinzu, 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 unmittelbar 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 "Kursleiterindex " aus, um die Kurse anzuzeigen, die den einzelnen Kursleitern zugewiesen sind.

Klicken Sie auf "Bearbeiten" eines Kursleiters, um die Seite "Bearbeiten" anzuzeigen.

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

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

DeleteConfirmed aktualisieren

Löschen Sie in InstructorController.cs die DeleteConfirmed Methode, und fügen Sie den folgenden Code an dessen Stelle 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 ändert die folgende Änderung:

  • Wenn der Kursleiter als Administrator einer Abteilung zugewiesen ist, wird die Aufgabe des Kursleiters aus dieser Abteilung entfernt. Ohne diesen Code erhalten Sie einen Fehler bei der referenziellen Integrität, wenn Sie versucht haben, 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 wurde. Im letzten Lernprogramm fügen Sie Code hinzu, der verhindert, dass dieses Szenario stattfindet.

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

Löschen Sie in InstructorController.cs die HttpGet und HttpPost Create die Methoden, 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 Edit-Methoden gesehen haben, mit der Ausnahme, dass anfänglich keine Kurse ausgewählt sind. Die HttpGet Create Methode ruft die PopulateAssignedCourseData Methode nicht auf, da möglicherweise Kurse ausgewählt sind, aber um eine leere Auflistung für die Schleife in der foreach Ansicht bereitzustellen (andernfalls löst der Ansichtscode eine NULL-Verweis-Ausnahme aus).

Die HttpPost Create-Methode fügt jeden ausgewählten Kurs der Kursnavigationseigenschaft vor dem Vorlagencode hinzu, der auf Überprüfungsfehler überprüft und den neuen Kursleiter der Datenbank hinzufügt. Kurse werden auch dann hinzugefügt, wenn Modellfehler vorhanden sind, sodass beim Auftreten von Modellfehlern (z. B. dem Benutzerschlüssel ein ungültiges Datum) angezeigt wird, sodass beim erneuten Anzeigen der Seite mit einer Fehlermeldung alle vorgenommenen Kursauswahlen automatisch wiederhergestellt werden.

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 Bürostandort und Kurskontrollkästchen nach dem Einstellungsdatum und vor der Schaltfläche "Absenden " 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 Lernprogramm zur grundlegenden CRUD-Funktionalität erläutert, implementiert entity Framework standardmäßig implizit Transaktionen. Für 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 in ASP.NET Datenzugriff – Empfohlene Ressourcen.

Nächster Schritt

In diesem Tutorial:

  • Angepasste Kursseiten
  • Office zur Seite "Kursleiter" hinzugefügt
  • Kursen zur Seite "Kursleiter" hinzugefügt
  • DeleteConfirmed aktualisiert
  • Office-Standort und -Kurse zur Seite "Erstellen" hinzugefügt

Wechseln Sie zum nächsten Artikel, um zu erfahren, wie Sie ein asynchrones Programmiermodell implementieren.