Share via


Tutorial: Aktualisieren verwandter Daten – ASP.NET Core MVC mit EF Core

Mithilfe des letzten Tutorials haben Sie zugehörige Daten abgerufen. Mithilfe dieses Tutorials führen Sie hingegen Updates für verwandte Daten aus, indem Sie Felder mit Fremdschlüsseln sowie Navigationseigenschaften aktualisieren.

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

Course Edit page

Edit Instructor page

In diesem Tutorial:

  • Passen Sie die Seite „Kurse“ an
  • Fügen Sie die Seite „Bearbeiten“ für Dozenten hinzu
  • Fügen Sie der Seite „Bearbeiten“ Kurse hinzu
  • Aktualisieren Sie die Seite „Löschen“
  • Fügen Sie Bürostandort und Kurse der Seite „Erstellen“ hinzu

Voraussetzungen

Passen Sie die Seite „Kurse“ an

Wenn eine neue Course-Entität erstellt wird, muss sie über eine Beziehung zu einer vorhandenen Abteilung verfügen. 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 aus der CoursesController.cs-Datei die vier Methoden zum „Erstellen“ und „Bearbeiten“, und ersetzen Sie diese durch den folgenden Code:

public IActionResult Create()
{
    PopulateDepartmentsDropDownList();
    return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
    if (ModelState.IsValid)
    {
        _context.Add(course);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var course = await _context.Courses
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.CourseID == id);
    if (course == null)
    {
        return NotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var courseToUpdate = await _context.Courses
        .FirstOrDefaultAsync(c => c.CourseID == id);

    if (await TryUpdateModelAsync<Course>(courseToUpdate,
        "",
        c => c.Credits, c => c.DepartmentID, c => c.Title))
    {
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
        return RedirectToAction(nameof(Index));
    }
    PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
    return View(courseToUpdate);
}

Erstellen Sie nach der HttpPost-Methode Edit eine neue Methode, die für die Dropdownliste Informationen zum Fachbereich lädt.

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

Die PopulateDepartmentsDropDownList-Methode ruft eine nach Namen sortierte Liste aller Abteilungen ab, erstellt eine SelectList-Auflistung für die Dropdownliste und übergibt diese Auflistung an die Ansicht in ViewBag. 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 Taghilfsprogramm <select>. Dann weiß das Hilfsprogramm, dass es nach einem ViewBag-Objekt für eine SelectList mit dem Namen „DepartmentID“ suchen soll.

Die HttpGet-Methode Create ruft die PopulateDepartmentsDropDownList-Methode auf, ohne das ausgewählte Element festzulegen, da der Fachbereich für einen neuen Code noch nicht festgelegt wurde:

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

Die HttpGet-Methode Edit legt anhand der ID des Fachbereichs, die bereits dem Kurs zugewiesen wurde, der bearbeitet wird, das ausgewählte Element fest:

public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var course = await _context.Courses
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.CourseID == id);
    if (course == null)
    {
        return NotFound();
    }
    PopulateDepartmentsDropDownList(course.DepartmentID);
    return View(course);
}

Die HttpPost-Methoden für Create und Edit enthalten außerdem Code, der das ausgewählte Element festlegt, wenn die Seite nach einem Fehler erneut angezeigt wird. Dadurch wird gewährleistet, dass der Fachbereich nicht geändert wird, wenn eine Seite erneut angezeigt wird, um die Fehlermeldung anzuzeigen.

Hinzufügen von „.AsNoTracking“ zu den Methoden „Details“ und „Delete“

Fügen Sie AsNoTracking-Aufrufe in die Details und zu den HttpGet-Methoden Delete hinzu, um die Leistung der Kursdetails und Seiten zum „Löschen“ zu optimieren.

public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var course = await _context.Courses
        .Include(c => c.Department)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.CourseID == id);
    if (course == null)
    {
        return NotFound();
    }

    return View(course);
}
public async Task<IActionResult> Delete(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var course = await _context.Courses
        .Include(c => c.Department)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.CourseID == id);
    if (course == null)
    {
        return NotFound();
    }

    return View(course);
}

Ändern der Kursansichten

Fügen Sie in der Views/Courses/Create.cshtml-Datei die Option „Select Department“ (Fachbereich auswählen) zu der Dropdownliste Department (Fachbereich) hinzu, ändern Sie den Titel von DepartmentID in Department, und fügen Sie eine Validierungsmeldung hinzu.

<div class="form-group">
    <label asp-for="Department" class="control-label"></label>
    <select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
        <option value="">-- Select Department --</option>
    </select>
    <span asp-validation-for="DepartmentID" class="text-danger" />
</div>

Nehmen Sie in der Datei Views/Courses/Edit.cshtml dieselben Änderungen für das Feld „Department“ (Fachbereich) vor wie in der Datei Create.cshtml.

Fügen Sie in der Views/Courses/Edit.cshtml-Datei außerdem vor dem Feld Titel ein Feld für die Kursnummer hinzu. Da es sich bei der Kursnummer um einen Primärschlüssel handelt, wird dieser zwar angezeigt, kann aber nicht geändert werden.

<div class="form-group">
    <label asp-for="CourseID" class="control-label"></label>
    <div>@Html.DisplayFor(model => model.CourseID)</div>
</div>

In der Ansicht „Bearbeiten“ ist bereits eine ausgeblendetes Feld (<input type="hidden">) für die Kursnummer vorhanden. Wenn Sie ein <label>-Taghilfsprogramm hinzufügen, wird trotzdem ein ausgeblendetes Feld benötigt, da dieses nicht dazu beiträgt, dass die Kursnummer in den bereitgestellten Daten vorhanden ist, wenn der Benutzer auf der Seite Bearbeiten auf Speichern klickt.

Fügen Sie oben in der Views/Courses/Delete.cshtml-Datei ein Feld für die Kursnummer hinzu, und ändern Sie „Department ID“ (Fachbereichs-ID) in „Department Name“ (Fachbereichsname).

@model ContosoUniversity.Models.Course

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

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Department.Name)
        </dd>
    </dl>
    
    <form asp-action="Delete">
        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            <a asp-action="Index">Back to List</a>
        </div>
    </form>
</div>

Nehmen Sie in der Views/Courses/Details.cshtml-Datei dieselben Änderungen wie in der Delete.cshtml-Datei vor.

Testen der Kursseiten

Führen Sie die App aus, wählen Sie die Registerkarte Courses (Kurse) aus, klicken Sie auf Neu erstellen, und geben Sie die Daten für einen neuen Kurs ein:

Course Create page

Klicken Sie auf Erstellen. Die Indexseite des Kurses wird mit dem Kurs angezeigt, der neu zu der Liste hinzugefügt wurde. Der Fachbereichsname in der Indexseitenliste wurde der Navigationseigenschaft entnommen und deutet darauf hin, dass die Beziehung ordnungsgemäß festgelegt wurde.

Klicken Sie auf der Indexseite eines Kurses auf Bearbeiten.

Course Edit page

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

Fügen Sie die Seite „Bearbeiten“ für Dozenten hinzu

Bei der Bearbeitung eines Dozentendatensatzes sollten Sie auch die Bürozuweisung des Dozenten aktualisieren. Die Entität Instructor verfügt über eine 1:0..1-Beziehung mit der Entität OfficeAssignment. Das bedeutet, dass Ihr Code den folgenden Situationen standhalten muss:

  • Wenn der Benutzer die Bürozuweisung löscht, es für diese aber zuvor einen Wert gab, löschen Sie die Entität OfficeAssignment.

  • Wenn der Benutzer eine Bürozuweisung eingibt, diese aber zuvor leer war, erstellen Sie eine neue OfficeAssignment-Entität.

  • Wenn der Benutzer den Wert einer Bürozuweisung verändert, ändern Sie den Wert in eine bereits vorhandene OfficeAssignment-Entität.

Aktualisieren des Controllers des Dozenten

Ändern Sie in der Datei InstructorsController.cs den Code in der HttpGet-Methode Edit, sodass diese die OfficeAssignment-Navigationseigenschaft der Instructor-Entität lädt und AsNoTracking aufruft:

public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var instructor = await _context.Instructors
        .Include(i => i.OfficeAssignment)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);
    if (instructor == null)
    {
        return NotFound();
    }
    return View(instructor);
}

Ersetzen Sie die HttpPost-Methode Edit durch den folgenden Code, damit diese Updates für die Bürozuweisungen verarbeiten kann:

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var instructorToUpdate = await _context.Instructors
        .Include(i => i.OfficeAssignment)
        .FirstOrDefaultAsync(s => s.ID == id);

    if (await TryUpdateModelAsync<Instructor>(
        instructorToUpdate,
        "",
        i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
    {
        if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
        {
            instructorToUpdate.OfficeAssignment = null;
        }
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
        return RedirectToAction(nameof(Index));
    }
    return View(instructorToUpdate);
}

Der Code führt Folgendes aus:

  • Er ändert den Methodennamen auf EditPost, da die Signatur jetzt der Signatur der HttpGet-Methode Edit entspricht. Das ActionName-Attribut gibt an, dass die /Edit/-URL immer noch verwendet wird.

  • Ruft die aktuelle Entität Instructor von der Datenbank über Eager Loading für die Navigationseigenschaft OfficeAssignment ab. Dies entspricht dem Vorgang in der HttpGet-Methode Edit.

  • Aktualisiert die abgerufene Entität Instructor mit Werten aus der Modellbindung. Mithilfe der TryUpdateModel-Überladung können Sie die Eigenschaften deklarieren, die Sie hinzufügen möchten. Dadurch wird, wie im zweiten Tutorial beschrieben, vermieden, dass zu viele Angaben gemacht werden.

    if (await TryUpdateModelAsync<Instructor>(
        instructorToUpdate,
        "",
        i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.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.

Aktualisieren der Dozentenansicht „Bearbeiten“

Fügen Sie am Ende vor der Schaltfläche Speichern in der Views/Instructors/Edit.cshtml-Datei ein neues Feld hinzu, um den Standort des Büros zu bearbeiten:

<div class="form-group">
    <label asp-for="OfficeAssignment.Location" class="control-label"></label>
    <input asp-for="OfficeAssignment.Location" class="form-control" />
    <span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>

Führen Sie die App aus, klicken Sie erst auf die Registerkarte Dozenten und dann für einen Dozenten auf Bearbeiten. Ändern Sie den Standort des Büros, und klicken Sie auf Speichern.

Instructor Edit page

Fügen Sie der Seite „Bearbeiten“ Kurse hinzu

Dozenten können eine beliebige Anzahl von Kursen unterrichten. Jetzt erweitern Sie die Kursleiterseite „Edit“ (Bearbeiten), indem Sie die Möglichkeit hinzufügen, Kurszuweisungen mithilfe einer Gruppe von Kontrollkästchen zu ändern. Dies wird im folgenden Screenshot veranschaulicht:

Instructor Edit page with courses

Zwischen den Entitäten Course und Instructor besteht eine m:n-Beziehung. Wenn Sie Beziehungen hinzufügen und entfernen möchten, können Sie Entitäten aus der verknüpften Entitätenmenge CourseAssignments hinzufügen und entfernen.

Bei der Benutzeroberfläche, über die Sie ändern können, welchen Kursen ein Kursleiter zugeteilt ist, handelt es sich um eine Gruppe von Kontrollkästchen. Für jeden Kurs in der Datenbank wird ein Kontrollkästchen angezeigt. Die Kontrollkästchen, die Kursen entsprechen, denen der Kursleiter zugeteilt ist, sind aktiviert. Der Benutzer kann Kontrollkästchen aktivieren oder deaktivieren, um Kurszuweisungen zu ändern. Bei einer deutlich höheren Anzahl von Kursen sollten Sie besser eine andere Methode verwenden, um Daten in der Ansicht darzustellen. Sie sollten aber auch in diesem Fall dieselbe Bearbeitungsmethode verwenden, um eine verknüpfte Entität zum Erstellen und Löschen von Beziehungen zu verwenden.

Aktualisieren des Controllers des Dozenten

Zum Bereitstellen von Daten für die Ansicht mit der Liste von Kontrollkästchen verwenden Sie eine Ansichtsmodellklasse.

Erstellen Sie im Ordner SchoolViewModels die Datei AssignedCourseData.cs, und ersetzen Sie den vorhandenen Code durch den folgenden Code:

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

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

Ersetzen Sie in der Datei InstructorsController.cs die HttpGet-Methode Edit durch den folgenden Code. Die Änderungen werden hervorgehoben.

public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var instructor = await _context.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);
    if (instructor == null)
    {
        return NotFound();
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

private void PopulateAssignedCourseData(Instructor instructor)
{
    var allCourses = _context.Courses;
    var instructorCourses = new HashSet<int>(instructor.CourseAssignments.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)
        });
    }
    ViewData["Courses"] = viewModel;
}

Über den Code wird für die Courses-Navigationseigenschaft Eager Loading („eifriges Laden“) hinzugefügt und die neue PopulateAssignedCourseData-Methode aufgerufen, um über die Ansichtsmodellklasse AssignedCourseData Informationen für das Array von Kontrollkästchen 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. Die Kurse, die dem Dozenten zugewiesen werden, werden in einer HashSet-Auflistung dargestellt, um die Suche effizienter zu machen, wenn Sie überprüfen, ob ein Kurs dem Dozenten zugewiesen ist. Die Assigned-Eigenschaft ist für Kurse, denen der Dozent zugewiesen ist, auf TRUE festgelegt. Die Ansicht verwendet diese Eigenschaft, um zu bestimmen, welche Kontrollkästchen als aktiviert angezeigt werden sollen. Zuletzt wird die Liste in ViewData an die Ansicht ü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, und fügen Sie eine neue Methode hinzu, die ein Update für Courses-Navigationseigenschaft der Instructor-Entität ausführt.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
    if (id == null)
    {
        return NotFound();
    }

    var instructorToUpdate = await _context.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.CourseAssignments)
            .ThenInclude(i => i.Course)
        .FirstOrDefaultAsync(m => m.ID == id);

    if (await TryUpdateModelAsync<Instructor>(
        instructorToUpdate,
        "",
        i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
    {
        if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
        {
            instructorToUpdate.OfficeAssignment = null;
        }
        UpdateInstructorCourses(selectedCourses, instructorToUpdate);
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            ModelState.AddModelError("", "Unable to save changes. " +
                "Try again, and if the problem persists, " +
                "see your system administrator.");
        }
        return RedirectToAction(nameof(Index));
    }
    UpdateInstructorCourses(selectedCourses, instructorToUpdate);
    PopulateAssignedCourseData(instructorToUpdate);
    return View(instructorToUpdate);
}
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

Die Methodensignatur unterscheidet sich jetzt von der HttpGet-Methode Edit, wodurch der Methodenname von EditPost auf Edit geändert wird.

Da es in der Ansicht keine Auflistung der Course-Entitäten gibt, kann durch die Modellbindung kein automatisches Update für die CourseAssignments-Navigationseigenschaft ausgeführt werden. Verwenden Sie dafür die neue CourseAssignments-Methode anstatt die Modellbindung zu verwenden, um ein Update für die UpdateInstructorCourses-Navigationseigenschaft auszuführen. Aus diesem Grund müssen Sie die CourseAssignments-Eigenschaft von der Modellbindung ausschließen. Dafür muss der Code, der TryUpdateModel aufruft, nicht verändert werden, weil Sie die Überladung verwenden, die eine explizite Genehmigung erfordert, und CourseAssignments nicht in der Liste der einzuschließenden Elemente enthalten ist.

Wenn keine Kontrollkästchen aktiviert wurden, initialisiert der Code in UpdateInstructorCourses die Navigationseigenschaft CourseAssignments mit einer leeren Sammlung und gibt Folgendes zurück:

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

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 wurde, dieser Kurs jedoch nicht in der Instructor.CourseAssignments-Navigationseigenschaft vorhanden ist, wird dieser zur Sammlung in der Navigationseigenschaft hinzugefügt.

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

Wenn das Kontrollkästchen für einen Kurs nicht aktiviert wurde, aber der Kurs ist in der Navigationseigenschaft Instructor.CourseAssignments vorhanden, dann wird der Kurs aus der Navigationseigenschaft entfernt.

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
    if (selectedCourses == null)
    {
        instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
        return;
    }

    var selectedCoursesHS = new HashSet<string>(selectedCourses);
    var instructorCourses = new HashSet<int>
        (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
    foreach (var course in _context.Courses)
    {
        if (selectedCoursesHS.Contains(course.CourseID.ToString()))
        {
            if (!instructorCourses.Contains(course.CourseID))
            {
                instructorToUpdate.CourseAssignments.Add(new CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID = course.CourseID });
            }
        }
        else
        {

            if (instructorCourses.Contains(course.CourseID))
            {
                CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID == course.CourseID);
                _context.Remove(courseToRemove);
            }
        }
    }
}

Ausführen eines Updates für die Dozentenseiten

Fügen Sie in der Datei Views/Instructors/Edit.cshtml ein Feld Courses (Kurse) mit einem Array von Kontrollkästchen hinzu, indem Sie den folgenden Code direkt nach den div-Elementen für das Feld Office (Büro) und vor dem div-Element für die Schaltfläche Speichern einfügen.

Hinweis

Wenn Sie den Code in Visual Studio einfügen, werden Zeilenumbrüche möglicherweise so geändert, dass der Code unterbrochen wird. Wenn der Code nach dem Einfügen anders aussieht, drücken Sie einmal STRG+Z, um die automatische Formatierung rückgängig zu machen. Damit werden die Zeilenumbrüche korrigiert, damit sie dem entsprechen, was Sie hier sehen. 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. Drücken Sie, nachdem Sie den Block mit dem neuen Code ausgewählt haben, dreimal auf die TAB-Taste, um den neuen Code am vorhandenen Code auszurichten. Dieses Problem wurde in Visual Studio 2019 behoben.

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.Models.SchoolViewModels.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>

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. Alle Kontrollkästchen haben denselben Namen („selectedCourses“), was bei der Modellbindung deutlich macht, dass diese als Gruppe behandelt werden sollen. Das Wertattribut der einzelnen Kontrollkästchen ist auf den Wert von CourseID festgelegt. Wenn die Seite zurückgesendet wird, übergibt die Modellbindung ein Array an den Controller, das lediglich die CourseID-Werte der aktivierten Kontrollkästchen enthält.

Wenn die Kontrollkästchen zu Beginn gerendert werden, verfügen die Kästchen, die für Kurse stehen, die dem Kursleiter zugewiesen sind, über checked-Attribute, die diese aktivieren (also als aktiviert anzeigen).

Führen Sie die App aus, klicken Sie erst auf die Registerkarte Dozenten und dann für einen Dozenten auf Bearbeiten, um die Seite Bearbeiten anzuzeigen.

Instructor Edit page with courses

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

Hinweis

Der hier gewählte Ansatz für die Bearbeitung der Kursdaten von Dozenten wird empfohlen, wenn eine begrenzte Anzahl von Kursen verwendet wird. Bei umfangreicheren Auflistungen wären eine andere Benutzeroberfläche und eine andere Aktualisierungsmethode erforderlich.

Aktualisieren Sie die Seite „Löschen“

Löschen Sie aus der Datei InstructorsController.cs die DeleteConfirmed-Methode, und fügen Sie stattdessen den folgenden Code ein.

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    Instructor instructor = await _context.Instructors
        .Include(i => i.CourseAssignments)
        .SingleAsync(i => i.ID == id);

    var departments = await _context.Departments
        .Where(d => d.InstructorID == id)
        .ToListAsync();
    departments.ForEach(d => d.InstructorID = null);

    _context.Instructors.Remove(instructor);

    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

Durch diesen Code werden folgende Änderungen vorgenommen:

  • Er verwendet Eager Loading für die Navigationseigenschaft CourseAssignments. Sie müssen diese Eigenschaft hinzufügen. Ansonsten weiß EF nicht, dass es zugehörige CourseAssignment-Entitäten gibt und löscht diese nicht. Wenn Sie vermeiden möchten, diese lesen zu müssen, können Sie in der Datenbank eine Löschweitergabe konfigurieren.

  • Wenn der zu löschende Dozent als Administrator einer beliebigen Abteilung zugewiesen ist, wird die Dozentenzuweisung aus diesen Abteilungen entfernt.

Fügen Sie Bürostandort und Kurse der Seite „Erstellen“ hinzu

Löschen Sie in der Datei InstructorsController.cs die HttpGet- und HttpPost-Methoden Create, und fügen Sie anschließend stattdessen folgenden Code ein:

public IActionResult Create()
{
    var instructor = new Instructor();
    instructor.CourseAssignments = new List<CourseAssignment>();
    PopulateAssignedCourseData(instructor);
    return View();
}

// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
{
    if (selectedCourses != null)
    {
        instructor.CourseAssignments = new List<CourseAssignment>();
        foreach (var course in selectedCourses)
        {
            var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
            instructor.CourseAssignments.Add(courseToAdd);
        }
    }
    if (ModelState.IsValid)
    {
        _context.Add(instructor);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    PopulateAssignedCourseData(instructor);
    return View(instructor);
}

Dieser Code ähnelt dem Code, der für die Edit-Methode verwendet wurde. Allerdings sind nicht von Beginn an Kurse ausgewählt. Die HttpGet-Methode Create 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 gibt der Ansichtscode eine NULL-Verweisausnahme zurück.

Die HttpPost-Methode Create fügt jeden der ausgewählten Kurse zu der CourseAssignments-Navigationseigenschaft hinzu, bevor sie nach Validierungsfehlern sucht und den neuen Dozenten zu der Datenbank hinzufügt. Kurse werden auch hinzugefügt, wenn es Modellfehler gibt, sodass alle zuvor ausgewählten Kurse automatisch wieder ausgewählt werden, wenn es zu Modellfehlern kommt (weil z.B. der Benutzer ein ungültiges Datum verschlüsselt hat) und die Seite mit Fehlermeldungen erneut dargestellt wird.

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

instructor.CourseAssignments = new List<CourseAssignment>();

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

private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
    get
    {
        return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
    }
    set
    {
        _courseAssignments = value;
    }
}

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

Fügen die in der Datei Views/Instructor/Create.cshtml ein Textfeld für den Bürostandort und Kontrollkästchen für Kurse hinzu, bevor Sie auf „Übermitteln“ klicken. Wenn Visual Studio den Code neu formatiert, wenn Sie diesen einfügen, beheben Sie die Formatierung auf dieselbe Weise wie auf der Seite „Bearbeiten“.

<div class="form-group">
    <label asp-for="OfficeAssignment.Location" class="control-label"></label>
    <input asp-for="OfficeAssignment.Location" class="form-control" />
    <span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.Models.SchoolViewModels.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>

Führen Sie einen Test durch, indem Sie die App ausführen und einen Dozenten erstellen.

Verarbeiten von Transaktionen

Wie bereits im CRUD-Tutorial erläutert, implementiert Entity Framework implizit Transaktionen. Informationen zu Szenarios, die Sie genauer kontrollieren müssen (z.B. wenn Sie Vorgänge einfügen möchten, die außerhalb von Entity Framework in einer Transaktion ausgeführt werden), finden Sie unter Transaktionen.

Abrufen des Codes

Download or view the completed app (Herunterladen oder anzeigen der vollständigen App).

Nächste Schritte

In diesem Tutorial:

  • Haben Sie die Seite „Kurse“ angepasst
  • Haben Sie die Seite „Bearbeiten“ für Dozenten hinzugefügt
  • Haben Sie der Seite „Bearbeiten“ Kurse hinzugefügt
  • Haben Sie die Seite „Löschen“ aktualisiert
  • Haben Sie Bürostandort und Kurse der Seite „Erstellen“ hinzugefügt

Fahren Sie mit dem nächsten Tutorial fort, um zu erfahren, wie Sie Nebenläufigkeitskonflikte behandeln können.