Samouczek: aktualizowanie powiązanych danych — ASP.NET MVC za pomocą polecenia EF Core
W poprzednim samouczku przedstawiono powiązane dane; W tym samouczku zaktualizujesz powiązane dane, aktualizując pola kluczy obcych i właściwości nawigacji.
Na poniższych ilustracjach przedstawiono niektóre strony, z którymi będziesz pracować.
W tym samouczku zostały wykonane następujące czynności:
- Dostosowywanie stron kursów
- Dodawanie strony edycji instruktorów
- Dodawanie kursów do strony Edytowanie
- Aktualizowanie strony Usuń
- Dodawanie lokalizacji biura i kursów do strony Tworzenie
Wymagania wstępne
Dostosowywanie stron kursów
Po utworzeniu nowej Course
jednostki musi ona mieć relację z istniejącym działem. Aby to ułatwić, kod szkieletowy zawiera metody kontrolera oraz widoki tworzenia i edytowania, które zawierają listę rozwijaną do wybierania działu. Lista rozwijana ustawia właściwość klucza obcego Course.DepartmentID
, a to wszystko, co wymaga programu Entity Framework, aby załadować Department
właściwość nawigacji z odpowiednią Department
jednostką. Użyjesz kodu szkieletowego, ale zmienisz go nieco, aby dodać obsługę błędów i posortować listę rozwijaną.
W CoursesController.cs
pliku usuń cztery metody Create and Edit i zastąp je następującym kodem:
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);
}
Po metodzie Edit
HttpPost utwórz nową metodę, która ładuje informacje o dziale dla listy rozwijanej.
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);
}
Metoda PopulateDepartmentsDropDownList
pobiera listę wszystkich działów posortowanych według nazwy, tworzy SelectList
kolekcję listy rozwijanej i przekazuje kolekcję do widoku w ViewBag
programie . Metoda akceptuje opcjonalny selectedDepartment
parametr, który umożliwia kod wywołujący określenie elementu, który zostanie wybrany podczas renderowania listy rozwijanej. Widok przekaże nazwę "DepartmentID" do pomocnika tagu <select>
, a pomocnik następnie wie, aby wyszukać w ViewBag
obiekcie SelectList
o nazwie "DepartmentID".
Metoda HttpGet Create
wywołuje metodę PopulateDepartmentsDropDownList
bez ustawiania wybranego elementu, ponieważ dla nowego kursu dział nie został jeszcze ustanowiony:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
Metoda HttpGet Edit
ustawia wybrany element na podstawie identyfikatora działu, który jest już przypisany do edytowanego kursu:
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);
}
Metody HttpPost dla obu Create
tych metod, a Edit
także zawierają kod, który ustawia wybrany element podczas ponownego redysponuj stronę po błędzie. Gwarantuje to, że po ponownym uruchomieniu strony w celu wyświetlenia komunikatu o błędzie wybrany dział pozostanie wybrany.
Dodaj. AsNoTracking do metod Details i Delete
Aby zoptymalizować wydajność stron Szczegóły kursu i Usuń, dodaj AsNoTracking
wywołania metod Details
i HttpGet Delete
.
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);
}
Modyfikowanie widoków kursu
W Views/Courses/Create.cshtml
programie dodaj opcję "Wybierz dział" do listy rozwijanej Dział, zmień podpis z Identyfikator działu na Dział i dodaj komunikat weryfikacji.
<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>
W Views/Courses/Edit.cshtml
pliku wprowadź tę samą zmianę w polu Dział, które zostało właśnie w Create.cshtml
pliku .
Ponadto w pliku Views/Courses/Edit.cshtml
dodaj pole numer kursu przed polem Tytuł . Ponieważ numer kursu jest kluczem podstawowym, jest wyświetlany, ale nie można go zmienić.
<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>
W widoku Edycja istnieje już ukryte pole (<input type="hidden">
) dla numeru kursu. <label>
Dodanie pomocnika tagów nie eliminuje potrzeby ukrytego pola, ponieważ nie powoduje, że numer kursu zostanie uwzględniony w opublikowanych danych, gdy użytkownik kliknie przycisk Zapisz na stronie Edytuj.
W Views/Courses/Delete.cshtml
pliku dodaj pole numer kursu u góry i zmień identyfikator działu na nazwę działu.
@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>
W Views/Courses/Details.cshtml
pliku wprowadź tę samą zmianę, którą właśnie wykonaliśmy dla elementu Delete.cshtml
.
Testowanie stron kursu
Uruchom aplikację, wybierz kartę Kursy , kliknij pozycję Utwórz nowy i wprowadź dane dla nowego kursu:
Kliknij pozycję Utwórz. Zostanie wyświetlona strona Indeks kursów z nowym kursem dodanym do listy. Nazwa działu na liście stron indeksu pochodzi z właściwości nawigacji pokazującej, że relacja została prawidłowo ustanowiona.
Kliknij pozycję Edytuj na kursie na stronie Indeks kursów.
Zmień dane na stronie i kliknij przycisk Zapisz. Strona Indeks kursów jest wyświetlana ze zaktualizowanymi danymi kursu.
Dodawanie strony edycji instruktorów
Podczas edytowania rekordu instruktora chcesz mieć możliwość zaktualizowania przydziału biura instruktora. Jednostka Instructor
ma relację jeden do zera lub jednego z jednostką OfficeAssignment
, co oznacza, że kod musi obsługiwać następujące sytuacje:
Jeśli użytkownik wyczyści przypisanie pakietu Office i pierwotnie miał wartość, usuń
OfficeAssignment
jednostkę.Jeśli użytkownik wprowadzi wartość przypisania pakietu Office i pierwotnie był pusty, utwórz nową
OfficeAssignment
jednostkę.Jeśli użytkownik zmieni wartość przypisania pakietu Office, zmień wartość w istniejącej
OfficeAssignment
jednostce.
Aktualizowanie kontrolera instruktorów
W InstructorsController.cs
pliku zmień kod w metodzie HttpGet Edit
, aby załadować właściwość nawigacji jednostki OfficeAssignment
Instruktor i wywołać metodę AsNoTracking
:
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);
}
Zastąp metodę HttpPost Edit
następującym kodem, aby obsługiwać aktualizacje przypisań pakietu Office:
[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);
}
Kod wykonuje następujące czynności:
Zmienia nazwę metody na
EditPost
, ponieważ podpis jest teraz taki sam jak metoda HttpGetEdit
(ActionName
atrybut określa, że/Edit/
adres URL jest nadal używany).Pobiera bieżącą
Instructor
jednostkę z bazy danych przy użyciu chętnegoOfficeAssignment
ładowania dla właściwości nawigacji. Jest to takie samo, jak w metodzie HttpGetEdit
.Aktualizacje pobraną
Instructor
jednostkę z wartościami z powiązania modelu. PrzeciążenieTryUpdateModel
umożliwia zadeklarowanie właściwości, które chcesz uwzględnić. Zapobiega to nadmiernemu delegowaniu, jak wyjaśniono w drugim samouczku.if (await TryUpdateModelAsync<Instructor>( instructorToUpdate, "", i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
Jeśli lokalizacja biura jest pusta, ustaw
Instructor.OfficeAssignment
właściwość na null, aby powiązany wiersz wOfficeAssignment
tabeli został usunięty.if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location)) { instructorToUpdate.OfficeAssignment = null; }
Zapisuje zmiany w bazie danych.
Aktualizowanie widoku Edycji instruktora
W Views/Instructors/Edit.cshtml
pliku dodaj nowe pole do edytowania lokalizacji biura na końcu przed przyciskiem Zapisz :
<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>
Uruchom aplikację, wybierz kartę Instruktorzy , a następnie kliknij pozycję Edytuj na instruktorze. Zmień lokalizację pakietu Office i kliknij przycisk Zapisz.
Dodawanie kursów do strony Edytowanie
Instruktorzy mogą uczyć dowolną liczbę kursów. Teraz ulepszysz stronę Edycja instruktora, dodając możliwość zmiany przypisań kursu przy użyciu grupy pól wyboru, jak pokazano na poniższym zrzucie ekranu:
Relacja między jednostkami i Course
Instructor
jest wiele do wielu. Aby dodawać i usuwać relacje, należy dodawać i usuwać jednostki do i z CourseAssignments
zestawu jednostek sprzężenia.
Interfejs użytkownika, który umożliwia zmianę kursów przypisanych przez instruktora, jest grupą pól wyboru. Zostanie wyświetlone pole wyboru dla każdego kursu w bazie danych, a te, do których jest obecnie przypisany instruktor. Użytkownik może zaznaczyć lub wyczyścić pola wyboru, aby zmienić przydziały kursu. Jeśli liczba kursów była znacznie większa, prawdopodobnie chcesz użyć innej metody prezentowania danych w widoku, ale użyjesz tej samej metody manipulowania jednostką sprzężenia w celu utworzenia lub usunięcia relacji.
Aktualizowanie kontrolera instruktorów
Aby podać dane do widoku listy pól wyboru, użyjesz klasy modelu widoku.
Utwórz AssignedCourseData.cs
w folderze SchoolViewModels i zastąp istniejący kod następującym kodem:
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; }
}
}
W InstructorsController.cs
pliku zastąp metodę HttpGet Edit
następującym kodem. Zmiany są wyróżnione.
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;
}
Kod dodaje chętne ładowanie dla Courses
właściwości nawigacji i wywołuje nową PopulateAssignedCourseData
metodę w celu udostępnienia informacji dla tablicy pól wyboru przy użyciu AssignedCourseData
klasy modelu widoku.
Kod w metodzie PopulateAssignedCourseData
odczytuje wszystkie Course
jednostki, aby załadować listę kursów przy użyciu klasy modelu widoku. Dla każdego kursu kod sprawdza, czy kurs istnieje we właściwości nawigacji instruktora Courses
. Aby utworzyć efektywne wyszukiwanie podczas sprawdzania, czy kurs jest przypisany do instruktora, kursy przypisane do instruktora HashSet
są umieszczane w kolekcji. Właściwość jest ustawiona Assigned
na wartość true dla kursów, do których jest przypisany instruktor. Widok użyje tej właściwości, aby określić, które pola wyboru muszą być wyświetlane jako zaznaczone. Na koniec lista jest przekazywana do widoku w ViewData
pliku .
Następnie dodaj kod, który jest wykonywany, gdy użytkownik kliknie przycisk Zapisz. Zastąp metodę EditPost
poniższym kodem i dodaj nową metodę, która aktualizuje Courses
właściwość nawigacji jednostki Instruktor.
[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);
}
}
}
}
Sygnatura metody różni się teraz od metody HttpGet Edit
, więc nazwa metody zmienia się z EditPost
powrotem na Edit
.
Ponieważ widok nie ma kolekcji jednostek Course, powiązanie modelu nie może automatycznie zaktualizować CourseAssignments
właściwości nawigacji. Zamiast używać powiązania modelu do aktualizowania CourseAssignments
właściwości nawigacji, należy to zrobić w nowej UpdateInstructorCourses
metodzie. W związku z tym należy wykluczyć CourseAssignments
właściwość z powiązania modelu. Nie wymaga to żadnych zmian w kodzie, który wywołuje TryUpdateModel
, ponieważ używasz przeciążenia, które wymaga jawnego zatwierdzenia i CourseAssignments
nie znajduje się na liście dołączania.
Jeśli nie zaznaczono żadnych pól wyboru, kod inicjuje UpdateInstructorCourses
CourseAssignments
właściwość nawigacji z pustą kolekcją i zwraca następujące elementy:
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);
}
}
}
}
Następnie kod przechodzi przez wszystkie kursy w bazie danych i sprawdza każdy kurs względem aktualnie przypisanych do instruktora w porównaniu z tymi, które zostały wybrane w widoku. Aby ułatwić wydajne wyszukiwanie, dwie ostatnie kolekcje są przechowywane w HashSet
obiektach.
Jeśli pole wyboru kursu zostało wybrane, ale kurs nie znajduje się we Instructor.CourseAssignments
właściwości nawigacji, kurs zostanie dodany do kolekcji we właściwości nawigacji.
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);
}
}
}
}
Jeśli pole wyboru kursu nie zostało zaznaczone, ale kurs znajduje się we Instructor.CourseAssignments
właściwości nawigacji, kurs zostanie usunięty z właściwości nawigacji.
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);
}
}
}
}
Aktualizowanie widoków instruktora
W Views/Instructors/Edit.cshtml
pliku dodaj pole Courses z tablicą pól wyboru, dodając następujący kod bezpośrednio po div
elementach pola Office i przed div
elementem przycisku Zapisz .
Uwaga
Podczas wklejania kodu w programie Visual Studio podziały wierszy mogą zostać zmienione w sposób, który przerywa kod. Jeśli kod wygląda inaczej po wklejeniu, naciśnij klawisze Ctrl+Z raz, aby cofnąć automatyczne formatowanie. Spowoduje to naprawienie podziałów wierszy tak, aby wyglądały jak to, co widzisz tutaj. Wcięcie nie musi być idealne, ale @:</tr><tr>
wiersze , @:<td>
, @:</td>
i @:</tr>
muszą znajdować się w jednym wierszu, jak pokazano lub wystąpi błąd środowiska uruchomieniowego. Po wybraniu bloku nowego kodu naciśnij klawisz Tab trzy razy, aby ustawić nowy kod przy użyciu istniejącego kodu. Ten problem został rozwiązany w programie Visual Studio 2019.
<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>
Ten kod tworzy tabelę HTML zawierającą trzy kolumny. W każdej kolumnie znajduje się pole wyboru, po którym następuje podpis składający się z numeru kursu i tytułu. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"), która informuje powiązanie modelu, że mają być traktowane jako grupa. Atrybut wartości każdego pola wyboru jest ustawiony na wartość CourseID
. Po opublikowaniu strony powiązanie modelu przekazuje tablicę do kontrolera składającego się z CourseID
wartości tylko zaznaczonych pól wyboru.
Gdy pola wyboru są początkowo renderowane, te, które są przeznaczone dla kursów przypisanych do instruktora, sprawdziły atrybuty, które je wybierają (wyświetla je zaznaczone).
Uruchom aplikację, wybierz kartę Instruktorzy, a następnie kliknij pozycję Edytuj na instruktorze, aby wyświetlić stronę Edytuj.
Zmień niektóre przydziały kursu i kliknij przycisk Zapisz. Wprowadzone zmiany zostaną odzwierciedlone na stronie Indeks.
Uwaga
Podejście podjęte tutaj do edytowania danych kursu instruktora działa dobrze, gdy istnieje ograniczona liczba kursów. W przypadku kolekcji, które są znacznie większe, wymagany jest inny interfejs użytkownika i inna metoda aktualizacji.
Aktualizowanie strony Usuń
W InstructorsController.cs
pliku usuń metodę DeleteConfirmed
i wstaw następujący kod w swoim miejscu.
[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));
}
Ten kod wprowadza następujące zmiany:
Wykonuje chętne
CourseAssignments
ładowanie dla właściwości nawigacji. Musisz uwzględnić tę opcję lub program EF nie będzie wiedział o powiązanychCourseAssignment
jednostkach i nie będzie ich usuwać. Aby uniknąć konieczności odczytywania ich tutaj, możesz skonfigurować usuwanie kaskadowe w bazie danych.Jeśli instruktor do usunięcia zostanie przypisany jako administrator jakichkolwiek działów, usuwa przydział instruktora z tych działów.
Dodawanie lokalizacji biura i kursów do strony Tworzenie
W InstructorsController.cs
pliku usuń metody HttpGet i HttpPost Create
, a następnie dodaj następujący kod w ich miejscu:
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);
}
Ten kod jest podobny do tego, co pokazano dla Edit
metod, z tą różnicą, że początkowo nie wybrano żadnych kursów. Metoda HttpGet Create
wywołuje metodę PopulateAssignedCourseData
nie dlatego, że można wybrać kursy, ale w celu udostępnienia pustej kolekcji dla foreach
pętli w widoku (w przeciwnym razie kod widoku zgłosi wyjątek odwołania o wartości null).
Metoda HttpPost Create
dodaje każdy wybrany kurs do CourseAssignments
właściwości nawigacji przed sprawdzeniem błędów walidacji i dodaje nowego instruktora do bazy danych. Kursy są dodawane nawet wtedy, gdy występują błędy modelu, tak aby w przypadku wystąpienia błędów modelu (na przykład użytkownik kluczował nieprawidłową datę), a strona jest odtwarzana z komunikatem o błędzie, wszystkie dokonane wybory kursu zostaną automatycznie przywrócone.
Zwróć uwagę, że aby można było dodać kursy do CourseAssignments
właściwości nawigacji, musisz zainicjować właściwość jako pustą kolekcję:
instructor.CourseAssignments = new List<CourseAssignment>();
Zamiast tego w kodzie kontrolera można to zrobić w Instructor
modelu, zmieniając metodę pobierania właściwości, aby automatycznie utworzyć kolekcję, jeśli nie istnieje, jak pokazano w poniższym przykładzie:
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}
Jeśli zmodyfikujesz CourseAssignments
właściwość w ten sposób, możesz usunąć jawny kod inicjowania właściwości w kontrolerze.
W Views/Instructor/Create.cshtml
pliku dodaj pole tekstowe lokalizacji biura i pola wyboru dla kursów przed przyciskiem Prześlij. Tak jak w przypadku strony Edycja, popraw formatowanie, jeśli program Visual Studio ponownie sformatowa kod podczas wklejania.
<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>
Przetestuj, uruchamiając aplikację i tworząc instruktora.
Obsługa transakcji
Jak wyjaśniono w samouczku CRUD, program Entity Framework niejawnie implementuje transakcje. W przypadku scenariuszy, w których potrzebujesz większej kontroli — na przykład jeśli chcesz uwzględnić operacje wykonywane poza platformą Entity Framework w transakcji — zobacz Transakcje.
Uzyskiwanie kodu
Pobierz lub wyświetl ukończoną aplikację.
Następne kroki
W tym samouczku zostały wykonane następujące czynności:
- Strony dostosowanych kursów
- Dodano stronę edycji instruktorów
- Dodano kursy do strony Edytowanie
- Zaktualizowano stronę Usuwania
- Dodano lokalizację biura i kursy do strony Tworzenie
Przejdź do następnego samouczka, aby dowiedzieć się, jak obsługiwać konflikty współbieżności.