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.
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:
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.
Ä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-MethodeEdit
entspricht. DasActionName
-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 NavigationseigenschaftOfficeAssignment
ab. Dies entspricht dem Vorgang in der HttpGet-MethodeEdit
.Aktualisiert die abgerufene Entität
Instructor
mit Werten aus der Modellbindung. Mithilfe derTryUpdateModel
-Ü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 derOfficeAssignment
-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.
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:
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.
Ä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örigeCourseAssignment
-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.