Udostępnij za pośrednictwem


Część 7, Razor strony z EF Core ASP.NET Core — aktualizowanie powiązanych danych

Przez Tom Dykstra, Jon P Smith i Rick Anderson

Aplikacja internetowa Contoso University pokazuje, jak tworzyć Razor aplikacje internetowe stron przy użyciu programu EF Core Visual Studio. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek.

Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację i porównaj ten kod z utworzonymi elementami, wykonując czynności opisane w samouczku.

W tym samouczku pokazano, jak zaktualizować powiązane dane. Na poniższych ilustracjach przedstawiono niektóre z ukończonych stron.

Strona edycji kursuStrona edycji instruktora

Aktualizowanie stron tworzenia i edytowania kursu

Kod szkieletowy dla stron Tworzenie i edytowanie kursu zawiera listę rozwijaną Dział z listą rozwijaną , DepartmentIDna przykład int. Na liście rozwijanej powinna być wyświetlana nazwa działu, więc obie te strony wymagają listy nazw działów. Aby podać listę, użyj klasy bazowej dla stron Tworzenie i edytowanie.

Tworzenie klasy bazowej na potrzeby tworzenia i edytowania kursu

Pages/Courses/DepartmentNamePageModel.cs Utwórz plik z następującym kodem:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                nameof(Department.DepartmentID),
                nameof(Department.Name),
                selectedDepartment);
        }
    }
}

Powyższy kod tworzy obiekt , SelectList aby zawierać listę nazw działów. Jeśli selectedDepartment zostanie określony, ten dział zostanie wybrany w elemecie SelectList.

Klasy modelu tworzenia i edytowania stron będą pochodzić z klasy DepartmentNamePageModel.

Aktualizowanie modelu strony Tworzenie kursu

Kurs jest przypisywany do działu. Klasa bazowa dla stron Tworzenie i Edytowanie udostępnia SelectList element do wybierania działu. Lista rozwijana, która używa SelectList właściwości klucza obcego Course.DepartmentID (FK). EF Core używa klucza Course.DepartmentID FK do załadowania Department właściwości nawigacji.

Tworzenie kursu

Zaktualizuj Pages/Courses/Create.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.

Powyższy kod ma następujące działanie:

  • Pochodzi z klasy DepartmentNamePageModel.
  • Używa TryUpdateModelAsync metody , aby zapobiec przesłonięć.
  • Usuwa element ViewData["DepartmentID"]. Jest DepartmentNameSL SelectList to silnie typowany model i będzie używany przez Razor stronę. Silnie typizowane modele są preferowane w przypadku słabych typów. Aby uzyskać więcej informacji, zobacz Weakly typed data (ViewData and ViewBag).

Aktualizowanie strony Tworzenie Razor kursu

Zaktualizuj Pages/Courses/Create.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy kod wprowadza następujące zmiany:

  • Zmienia podpis z Identyfikator działu na Dział.
  • Zamienia wartość "ViewBag.DepartmentID" na DepartmentNameSL (z klasy bazowej).
  • Dodaje opcję "Wybierz dział". Ta zmiana powoduje renderowanie pozycji "Wybierz dział" na liście rozwijanej, gdy żaden dział nie został jeszcze wybrany, a nie pierwszy dział.
  • Dodaje komunikat weryfikacji, gdy dział nie jest wybrany.

Strona Razor używa pomocnika Wybierz tag:

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

Przetestuj stronę Tworzenie. Na stronie Tworzenie zostanie wyświetlona nazwa działu, a nie identyfikator działu.

Aktualizowanie modelu strony Edycja kursu

Zaktualizuj Pages/Courses/Edit.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

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

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
            return Page();
        }

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

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (courseToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

Zmiany są podobne do tych wprowadzonych w modelu tworzenia strony. W poprzednim kodzie PopulateDepartmentsDropDownList przekazuje identyfikator działu, który wybiera ten dział na liście rozwijanej.

Aktualizowanie strony Edytowanie Razor kursu

Zaktualizuj Pages/Courses/Edit.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.EditModel

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

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy kod wprowadza następujące zmiany:

  • Wyświetla identyfikator kursu. Ogólnie klucz podstawowy (PK) jednostki nie jest wyświetlany. Pakiety PKs są zwykle bez znaczenia dla użytkowników. W takim przypadku klucz PK jest numerem kursu.
  • Zmienia podpis listy rozwijanej Dział z Identyfikator działu na Dział.
  • "ViewBag.DepartmentID" Zastępuje element DepartmentNameSL, który znajduje się w klasie bazowej.

Strona zawiera ukryte pole (<input type="hidden">) dla numeru kursu. Dodanie pomocnika asp-for="Course.CourseID" tagów <label> w programie nie eliminuje potrzeby pola ukrytego. <input type="hidden"> jest wymagany, aby numer kursu został uwzględniony w opublikowanych danych, gdy użytkownik wybierze pozycję Zapisz.

Aktualizowanie modeli strony Kurs

AsNoTracking może zwiększyć wydajność, gdy śledzenie nie jest wymagane.

Zaktualizuj Pages/Courses/Delete.cshtml.cs metodę i Pages/Courses/Details.cshtml.cs dodając AsNoTracking je do OnGetAsync następujących metod:

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

    Course = await _context.Courses
        .AsNoTracking()
        .Include(c => c.Department)
        .FirstOrDefaultAsync(m => m.CourseID == id);

    if (Course == null)
    {
        return NotFound();
    }
    return Page();
}

Aktualizowanie stron kursu Razor

Zaktualizuj Pages/Courses/Delete.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
    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.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
    
    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Wprowadź te same zmiany na stronie Szczegóły.

@page
@model ContosoUniversity.Pages.Courses.DetailsModel

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

<h2>Details</h2>

<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Testowanie stron kursu

Przetestuj strony tworzenia, edytowania, szczegółów i usuwania.

Aktualizowanie stron tworzenia i edytowania instruktora

Instruktorzy mogą uczyć dowolną liczbę kursów. Na poniższej ilustracji przedstawiono stronę edycji instruktora z tablicą pól wyboru oczywiście.

Strona Edycji instruktora z kursami

Pola wyboru umożliwiają zmianę kursów, do których jest przypisany instruktor. Pole wyboru jest wyświetlane dla każdego kursu w bazie danych. Wybrane są kursy przypisane przez instruktora. 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, inny interfejs użytkownika może działać lepiej. Jednak metoda zarządzania relacją wiele do wielu pokazana tutaj nie uległa zmianie. Aby utworzyć lub usunąć relacje, manipulujesz jednostką sprzężenia.

Tworzenie klasy dla przypisanych danych kursów

Utwórz Models/SchoolViewModels/AssignedCourseData.cs za pomocą następującego kodu:

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

Klasa AssignedCourseData zawiera dane służące do tworzenia pól wyboru dla kursów przypisanych do instruktora.

Tworzenie klasy bazowej modelu strony instruktora

Utwórz klasę bazową Pages/Instructors/InstructorCoursesPageModel.cs :

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {
        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context,
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.Courses.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }
    }
}

Jest InstructorCoursesPageModel to klasa bazowa dla modeli stron Edycja i Tworzenie. PopulateAssignedCourseData odczytuje wszystkie Course jednostki w celu wypełnienia AssignedCourseDataList. Dla każdego kursu kod ustawia CourseIDtytuł , i czy instruktor jest przypisany do kursu. Zestaw hashset służy do wydajnego wyszukiwania.

Obsługa lokalizacji biura

Inną relacją, którą strona edycji musi obsłużyć, jest relacja "jeden do zera" lub "jeden", którą jednostka Instruktor ma z jednostką OfficeAssignment . Kod edycji instruktora musi obsługiwać następujące scenariusze:

  • Jeśli użytkownik wyczyści przypisanie pakietu Office, usuń OfficeAssignment jednostkę.
  • Jeśli użytkownik wprowadzi przypisanie pakietu Office i był pusty, utwórz nową OfficeAssignment jednostkę.
  • Jeśli użytkownik zmieni przypisanie pakietu Office, zaktualizuj OfficeAssignment jednostkę.

Aktualizowanie modelu strony Edycji instruktora

Zaktualizuj Pages/Instructors/Edit.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class EditModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

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

            Instructor = await _context.Instructors
                .Include(i => i.OfficeAssignment)
                .Include(i => i.Courses)
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            PopulateAssignedCourseData(_context, Instructor);
            return Page();
        }

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

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

            if (instructorToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Instructor>(
                instructorToUpdate,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                if (String.IsNullOrWhiteSpace(
                    instructorToUpdate.OfficeAssignment?.Location))
                {
                    instructorToUpdate.OfficeAssignment = null;
                }
                UpdateInstructorCourses(selectedCourses, instructorToUpdate);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            UpdateInstructorCourses(selectedCourses, instructorToUpdate);
            PopulateAssignedCourseData(_context, instructorToUpdate);
            return Page();
        }

        public void UpdateInstructorCourses(string[] selectedCourses,
                                            Instructor instructorToUpdate)
        {
            if (selectedCourses == null)
            {
                instructorToUpdate.Courses = new List<Course>();
                return;
            }

            var selectedCoursesHS = new HashSet<string>(selectedCourses);
            var instructorCourses = new HashSet<int>
                (instructorToUpdate.Courses.Select(c => c.CourseID));
            foreach (var course in _context.Courses)
            {
                if (selectedCoursesHS.Contains(course.CourseID.ToString()))
                {
                    if (!instructorCourses.Contains(course.CourseID))
                    {
                        instructorToUpdate.Courses.Add(course);
                    }
                }
                else
                {
                    if (instructorCourses.Contains(course.CourseID))
                    {
                        var courseToRemove = instructorToUpdate.Courses.Single(
                                                        c => c.CourseID == course.CourseID);
                        instructorToUpdate.Courses.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

Powyższy kod ma następujące działanie:

  • Pobiera bieżącą Instructor jednostkę z bazy danych przy użyciu chętnego OfficeAssignment ładowania dla właściwości nawigacji i Courses .
  • Aktualizuje pobraną Instructor jednostkę przy użyciu wartości z powiązania modelu. TryUpdateModelAsync zapobiega przesłonięć.
  • Jeśli lokalizacja biura jest pusta, ustawia wartość Instructor.OfficeAssignment null. Gdy Instructor.OfficeAssignment ma wartość null, powiązany wiersz w OfficeAssignment tabeli zostanie usunięty.
  • Wywołuje PopulateAssignedCourseData metodę w OnGetAsync celu podania informacji dotyczących pól wyboru przy użyciu AssignedCourseData klasy modelu widoku.
  • Wywołuje metodę UpdateInstructorCourses w OnPostAsync celu zastosowania informacji z pól wyboru do edytowanej jednostki Instruktor.
  • Wywołania PopulateAssignedCourseData i UpdateInstructorCourses w przypadku OnPostAsync TryUpdateModelAsync niepowodzenia. Ta metoda wywołuje metodę przywracania przypisanych danych kursów wprowadzonych na stronie, gdy jest ona odtwarzana ponownie z komunikatem o błędzie.

Razor Ponieważ strona nie ma kolekcji jednostek Course, powiązanie modelu nie może automatycznie zaktualizować Courses właściwości nawigacji. Zamiast używać powiązania modelu do aktualizowania Courses właściwości nawigacji, odbywa się to w nowej UpdateInstructorCourses metodzie. W związku z tym należy wykluczyć Courses właściwość z powiązania modelu. Nie wymaga to żadnej zmiany w kodzie wywołującym, TryUpdateModelAsync ponieważ używasz przeciążenia z zadeklarowanymi właściwościami i Courses nie znajduje się na liście dołączania.

Jeśli nie zaznaczono żadnych pól wyboru, kod inicjuje UpdateInstructorCourses instructorToUpdate.Courses element z pustą kolekcją i zwraca następujące elementy:

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

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 na stronie. Aby ułatwić wydajne wyszukiwanie, dwie ostatnie kolekcje są przechowywane w HashSet obiektach.

Jeśli pole wyboru kursu jest zaznaczone, ale kurs nie znajduje się we Instructor.Courses właściwości nawigacji, kurs zostanie dodany do kolekcji we właściwości nawigacji.

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

Jeśli pole wyboru kursu nie jest zaznaczone, ale kurs znajduje się we Instructor.Courses właściwości nawigacji, kurs zostanie usunięty z właściwości nawigacji.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        var courseToRemove = instructorToUpdate.Courses.Single(
                                        c => c.CourseID == course.CourseID);
        instructorToUpdate.Courses.Remove(courseToRemove);
    }
}

Aktualizowanie strony Edytowanie Razor instruktora

Zaktualizuj Pages/Instructors/Edit.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy kod tworzy tabelę HTML zawierającą trzy kolumny. Każda kolumna ma pole wyboru i podpis zawierający numer kursu i tytuł. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"). Użycie tej samej nazwy informuje powiązanie modelu, aby traktować je jako grupę. Atrybut wartości każdego pola wyboru jest ustawiony na CourseID. Po opublikowaniu strony powiązanie modelu przekazuje tablicę składającą się z CourseID wartości tylko zaznaczonych pól wyboru.

Gdy pola wyboru są początkowo renderowane, wybrane są kursy przypisane do instruktora.

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, inny interfejs użytkownika i inna metoda aktualizacji byłyby bardziej opłacalne i wydajne.

Uruchom aplikację i przetestuj zaktualizowaną stronę Edycji instruktorów. Zmień niektóre przydziały kursu. Zmiany są odzwierciedlane na stronie Indeks.

Aktualizowanie strony Tworzenie instruktora

Zaktualizuj model strony Tworzenie instruktora i za pomocą kodu podobnego do strony Edytuj:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<InstructorCoursesPageModel> _logger;

        public CreateModel(SchoolContext context,
                          ILogger<InstructorCoursesPageModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.Courses = new List<Course>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            var newInstructor = new Instructor();

            if (selectedCourses.Length > 0)
            {
                newInstructor.Courses = new List<Course>();
                // Load collection with one DB call.
                _context.Courses.Load();
            }

            // Add selected Courses courses to the new instructor.
            foreach (var course in selectedCourses)
            {
                var foundCourse = await _context.Courses.FindAsync(int.Parse(course));
                if (foundCourse != null)
                {
                    newInstructor.Courses.Add(foundCourse);
                }
                else
                {
                    _logger.LogWarning("Course {course} not found", course);
                }
            }

            try
            {
                if (await TryUpdateModelAsync<Instructor>(
                                newInstructor,
                                "Instructor",
                                i => i.FirstMidName, i => i.LastName,
                                i => i.HireDate, i => i.OfficeAssignment))
                {
                    _context.Instructors.Add(newInstructor);
                    await _context.SaveChangesAsync();
                    return RedirectToPage("./Index");
                }
                return RedirectToPage("./Index");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }

            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}

Powyższy kod ma następujące działanie:

  • Dodaje rejestrowanie dla komunikatów ostrzegawczych i komunikatów o błędach.

  • Wywołuje metodę Load, która pobiera wszystkie kursy w jednym wywołaniu bazy danych. W przypadku małych kolekcji jest to optymalizacja w przypadku korzystania z programu FindAsync. FindAsync Zwraca śledzonej jednostki bez żądania do bazy danych.

    public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
    {
        var newInstructor = new Instructor();
    
        if (selectedCourses.Length > 0)
        {
            newInstructor.Courses = new List<Course>();
            // Load collection with one DB call.
            _context.Courses.Load();
        }
    
        // Add selected Courses courses to the new instructor.
        foreach (var course in selectedCourses)
        {
            var foundCourse = await _context.Courses.FindAsync(int.Parse(course));
            if (foundCourse != null)
            {
                newInstructor.Courses.Add(foundCourse);
            }
            else
            {
                _logger.LogWarning("Course {course} not found", course);
            }
        }
    
        try
        {
            if (await TryUpdateModelAsync<Instructor>(
                            newInstructor,
                            "Instructor",
                            i => i.FirstMidName, i => i.LastName,
                            i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            return RedirectToPage("./Index");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex.Message);
        }
    
        PopulateAssignedCourseData(_context, newInstructor);
        return Page();
    }
    
  • _context.Instructors.Add(newInstructor) Tworzy nowe Instructor przy użyciu relacji wiele-do-wielu bez jawnego mapowania tabeli sprzężenia. W programie EF 5.0 dodano wiele do wielu.

Przetestuj stronę Tworzenie instruktora.

Zaktualizuj stronę Tworzenie Razor instruktora przy użyciu kodu podobnego do strony Edytuj:

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

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

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Aktualizowanie strony Usuwanie instruktora

Zaktualizuj Pages/Instructors/Delete.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

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

            Instructor = await _context.Instructors.FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

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

            Instructor instructor = await _context.Instructors
                .Include(i => i.Courses)
                .SingleAsync(i => i.ID == id);

            if (instructor == null)
            {
                return RedirectToPage("./Index");
            }

            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 RedirectToPage("./Index");
        }
    }
}

Powyższy kod wprowadza następujące zmiany:

  • Używa chętnego Courses ładowania dla właściwości nawigacji. Courses muszą być dołączone lub nie są usuwane po usunięciu instruktora. Aby uniknąć konieczności ich odczytywania, skonfiguruj 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.

Uruchom aplikację i przetestuj stronę Usuń.

Następne kroki

W tym samouczku pokazano, jak zaktualizować powiązane dane. Na poniższych ilustracjach przedstawiono niektóre z ukończonych stron.

Strona edycji kursuStrona edycji instruktora

Aktualizowanie stron tworzenia i edytowania kursu

Kod szkieletowy dla stron Tworzenie i edytowanie kursu zawiera listę rozwijaną Dział zawierającą identyfikator działu (liczbę całkowitą). Na liście rozwijanej powinna być wyświetlana nazwa działu, więc obie te strony wymagają listy nazw działów. Aby podać listę, użyj klasy bazowej dla stron Tworzenie i edytowanie.

Tworzenie klasy bazowej na potrzeby tworzenia i edytowania kursu

Pages/Courses/DepartmentNamePageModel.cs Utwórz plik z następującym kodem:

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                        "DepartmentID", "Name", selectedDepartment);
        }
    }
}

Powyższy kod tworzy obiekt , SelectList aby zawierać listę nazw działów. Jeśli selectedDepartment zostanie określony, ten dział zostanie wybrany w elemecie SelectList.

Klasy modelu tworzenia i edytowania stron będą pochodzić z klasy DepartmentNamePageModel.

Aktualizowanie modelu strony Tworzenie kursu

Kurs jest przypisywany do działu. Klasa bazowa dla stron Tworzenie i Edytowanie udostępnia SelectList element do wybierania działu. Lista rozwijana, która używa SelectList właściwości klucza obcego Course.DepartmentID (FK). EF Core używa klucza Course.DepartmentID FK do załadowania Department właściwości nawigacji.

Tworzenie kursu

Zaktualizuj Pages/Courses/Create.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.

Powyższy kod ma następujące działanie:

  • Pochodzi z klasy DepartmentNamePageModel.
  • Używa TryUpdateModelAsync metody , aby zapobiec przesłonięć.
  • Usuwa element ViewData["DepartmentID"]. DepartmentNameSL z klasy bazowej jest silnie typizowanego modelu i będzie używany przez Razor stronę. Silnie typizowane modele są preferowane w przypadku słabych typów. Aby uzyskać więcej informacji, zobacz Weakly typed data (ViewData and ViewBag).

Aktualizowanie strony Tworzenie Razor kursu

Zaktualizuj Pages/Courses/Create.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy kod wprowadza następujące zmiany:

  • Zmienia podpis z Identyfikator działu na Dział.
  • Zamienia wartość "ViewBag.DepartmentID" na DepartmentNameSL (z klasy bazowej).
  • Dodaje opcję "Wybierz dział". Ta zmiana powoduje renderowanie pozycji "Wybierz dział" na liście rozwijanej, gdy żaden dział nie został jeszcze wybrany, a nie pierwszy dział.
  • Dodaje komunikat weryfikacji, gdy dział nie jest wybrany.

Strona Razor używa pomocnika Wybierz tag:

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

Przetestuj stronę Tworzenie. Na stronie Tworzenie zostanie wyświetlona nazwa działu, a nie identyfikator działu.

Aktualizowanie modelu strony Edycja kursu

Zaktualizuj Pages/Courses/Edit.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

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

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
            return Page();
        }

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

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (courseToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

Zmiany są podobne do tych wprowadzonych w modelu tworzenia strony. W poprzednim kodzie PopulateDepartmentsDropDownList przekazuje identyfikator działu, który wybiera ten dział na liście rozwijanej.

Aktualizowanie strony Edytowanie Razor kursu

Zaktualizuj Pages/Courses/Edit.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.EditModel

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

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy kod wprowadza następujące zmiany:

  • Wyświetla identyfikator kursu. Ogólnie klucz podstawowy (PK) jednostki nie jest wyświetlany. Pakiety PKs są zwykle bez znaczenia dla użytkowników. W takim przypadku klucz PK jest numerem kursu.
  • Zmienia podpis listy rozwijanej Dział z Identyfikator działu na Dział.
  • Zamienia wartość "ViewBag.DepartmentID" na DepartmentNameSL (z klasy bazowej).

Strona zawiera ukryte pole (<input type="hidden">) dla numeru kursu. Dodanie pomocnika asp-for="Course.CourseID" tagów <label> w programie nie eliminuje potrzeby pola ukrytego. <input type="hidden"> jest wymagany, aby numer kursu został uwzględniony w opublikowanych danych, gdy użytkownik kliknie przycisk Zapisz.

Aktualizowanie stron szczegółów kursu i usuwania

AsNoTracking może zwiększyć wydajność, gdy śledzenie nie jest wymagane.

Aktualizowanie modeli strony Kurs

Zaktualizuj Pages/Courses/Delete.cshtml.cs element za pomocą następującego kodu, aby dodać AsNoTrackingpolecenie :

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

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

            Course = await _context.Courses
                .AsNoTracking()
                .Include(c => c.Department)
                .FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }
            return Page();
        }

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

            Course = await _context.Courses.FindAsync(id);

            if (Course != null)
            {
                _context.Courses.Remove(Course);
                await _context.SaveChangesAsync();
            }

            return RedirectToPage("./Index");
        }
    }
}

Wprowadź tę samą zmianę Pages/Courses/Details.cshtml.cs w pliku:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class DetailsModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DetailsModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public Course Course { get; set; }

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

            Course = await _context.Courses
                 .AsNoTracking()
                 .Include(c => c.Department)
                 .FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }
            return Page();
        }
    }
}

Aktualizowanie stron kursu Razor

Zaktualizuj Pages/Courses/Delete.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
    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.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
    
    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Wprowadź te same zmiany na stronie Szczegóły.

@page
@model ContosoUniversity.Pages.Courses.DetailsModel

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

<h2>Details</h2>

<div>
    <h4>Course</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Course.Department.Name)
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Testowanie stron kursu

Przetestuj strony tworzenia, edytowania, szczegółów i usuwania.

Aktualizowanie stron tworzenia i edytowania instruktora

Instruktorzy mogą uczyć dowolną liczbę kursów. Na poniższej ilustracji przedstawiono stronę edycji instruktora z tablicą pól wyboru oczywiście.

Strona Edycji instruktora z kursami

Pola wyboru umożliwiają zmianę kursów, do których jest przypisany instruktor. Pole wyboru jest wyświetlane dla każdego kursu w bazie danych. Wybrane są kursy przypisane przez instruktora. 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, inny interfejs użytkownika może działać lepiej. Jednak metoda zarządzania relacją wiele do wielu pokazana tutaj nie uległa zmianie. Aby utworzyć lub usunąć relacje, manipulujesz jednostką sprzężenia.

Tworzenie klasy dla przypisanych danych kursów

Utwórz Models/SchoolViewModels/AssignedCourseData.cs za pomocą następującego kodu:

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

Klasa AssignedCourseData zawiera dane służące do tworzenia pól wyboru dla kursów przypisanych do instruktora.

Tworzenie klasy bazowej modelu strony instruktora

Utwórz klasę bazową Pages/Instructors/InstructorCoursesPageModel.cs :

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {

        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context, 
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.CourseAssignments.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }

        public void UpdateInstructorCourses(SchoolContext context, 
            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
                                .SingleOrDefault(i => i.CourseID == course.CourseID);
                        context.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

Jest InstructorCoursesPageModel to klasa bazowa, która będzie używana dla modeli stron Edytowanie i tworzenie. PopulateAssignedCourseData odczytuje wszystkie Course jednostki w celu wypełnienia AssignedCourseDataList. Dla każdego kursu kod ustawia CourseIDtytuł , i czy instruktor jest przypisany do kursu. Zestaw hashset służy do wydajnego wyszukiwania.

Razor Ponieważ strona 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 żadnej zmiany w kodzie wywołującym, TryUpdateModel ponieważ używasz przeciążenia z zadeklarowanymi właściwościami 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:

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

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 na stronie. 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.

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.CourseAssignments.Add(
            new CourseAssignment
            {
                InstructorID = instructorToUpdate.ID,
                CourseID = course.CourseID
            });
    }
}

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.

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        CourseAssignment courseToRemove
            = instructorToUpdate
                .CourseAssignments
                .SingleOrDefault(i => i.CourseID == course.CourseID);
        context.Remove(courseToRemove);
    }
}

Obsługa lokalizacji biura

Inną relacją, którą strona edycji musi obsłużyć, jest relacja "jeden do zera" lub "jeden", którą jednostka Instruktor ma z jednostką OfficeAssignment . Kod edycji instruktora musi obsługiwać następujące scenariusze:

  • Jeśli użytkownik wyczyści przypisanie pakietu Office, usuń OfficeAssignment jednostkę.
  • Jeśli użytkownik wprowadzi przypisanie pakietu Office i był pusty, utwórz nową OfficeAssignment jednostkę.
  • Jeśli użytkownik zmieni przypisanie pakietu Office, zaktualizuj OfficeAssignment jednostkę.

Aktualizowanie modelu strony Edycji instruktora

Zaktualizuj Pages/Instructors/Edit.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class EditModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

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

            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(_context, Instructor);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(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(s => s.ID == id);

            if (instructorToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Instructor>(
                instructorToUpdate,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                if (String.IsNullOrWhiteSpace(
                    instructorToUpdate.OfficeAssignment?.Location))
                {
                    instructorToUpdate.OfficeAssignment = null;
                }
                UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
            PopulateAssignedCourseData(_context, instructorToUpdate);
            return Page();
        }
    }
}

Powyższy kod ma następujące działanie:

  • Pobiera bieżącą Instructor jednostkę z bazy danych przy użyciu chętnego OfficeAssignmentładowania dla właściwości nawigacji , CourseAssignmenti CourseAssignment.Course .
  • Aktualizuje pobraną Instructor jednostkę przy użyciu wartości z powiązania modelu. TryUpdateModel zapobiega przesłonięć.
  • Jeśli lokalizacja biura jest pusta, ustawia wartość Instructor.OfficeAssignment null. Gdy Instructor.OfficeAssignment ma wartość null, powiązany wiersz w OfficeAssignment tabeli zostanie usunięty.
  • Wywołuje PopulateAssignedCourseData metodę w OnGetAsync celu podania informacji dotyczących pól wyboru przy użyciu AssignedCourseData klasy modelu widoku.
  • Wywołuje metodę UpdateInstructorCourses w OnPostAsync celu zastosowania informacji z pól wyboru do edytowanej jednostki Instruktor.
  • Wywołania PopulateAssignedCourseData i UpdateInstructorCourses w przypadku OnPostAsync TryUpdateModel niepowodzenia. Ta metoda wywołuje metodę przywracania przypisanych danych kursów wprowadzonych na stronie, gdy jest ona odtwarzana ponownie z komunikatem o błędzie.

Aktualizowanie strony Edytowanie Razor instruktora

Zaktualizuj Pages/Instructors/Edit.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy kod tworzy tabelę HTML zawierającą trzy kolumny. Każda kolumna ma pole wyboru i podpis zawierający numer kursu i tytuł. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"). Użycie tej samej nazwy informuje powiązanie modelu, aby traktować je jako grupę. Atrybut wartości każdego pola wyboru jest ustawiony na CourseID. Po opublikowaniu strony powiązanie modelu przekazuje tablicę składającą się z CourseID wartości tylko zaznaczonych pól wyboru.

Gdy pola wyboru są początkowo renderowane, wybrane są kursy przypisane do instruktora.

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, inny interfejs użytkownika i inna metoda aktualizacji byłyby bardziej opłacalne i wydajne.

Uruchom aplikację i przetestuj zaktualizowaną stronę Edycji instruktorów. Zmień niektóre przydziały kursu. Zmiany są odzwierciedlane na stronie Indeks.

Aktualizowanie strony Tworzenie instruktora

Zaktualizuj model Razor strony i stronę Tworzenie instruktora przy użyciu kodu podobnego do strony Edycja:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.CourseAssignments = new List<CourseAssignment>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            var newInstructor = new Instructor();
            if (selectedCourses != null)
            {
                newInstructor.CourseAssignments = new List<CourseAssignment>();
                foreach (var course in selectedCourses)
                {
                    var courseToAdd = new CourseAssignment
                    {
                        CourseID = int.Parse(course)
                    };
                    newInstructor.CourseAssignments.Add(courseToAdd);
                }
            }

            if (await TryUpdateModelAsync<Instructor>(
                newInstructor,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);                
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}
@page
@model ContosoUniversity.Pages.Instructors.CreateModel

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

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Przetestuj stronę Tworzenie instruktora.

Aktualizowanie strony Usuwanie instruktora

Zaktualizuj Pages/Instructors/Delete.cshtml.cs za pomocą następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

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

            Instructor = await _context.Instructors.FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

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

            Instructor instructor = await _context.Instructors
                .Include(i => i.CourseAssignments)
                .SingleAsync(i => i.ID == id);

            if (instructor == null)
            {
                return RedirectToPage("./Index");
            }

            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 RedirectToPage("./Index");
        }
    }
}

Powyższy kod wprowadza następujące zmiany:

  • Używa chętnego CourseAssignments ładowania dla właściwości nawigacji. CourseAssignments muszą być dołączone lub nie są usuwane po usunięciu instruktora. Aby uniknąć konieczności ich odczytywania, skonfiguruj 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.

Uruchom aplikację i przetestuj stronę Usuń.

Następne kroki

W tym samouczku przedstawiono aktualizowanie powiązanych danych. Jeśli napotkasz problemy, nie możesz rozwiązać, pobierz lub wyświetl ukończoną aplikację. Pobierz instrukcje.

Na poniższych ilustracjach przedstawiono niektóre z ukończonych stron.

Strona edycji kursuStrona edycji instruktora

Sprawdź i przetestuj strony Tworzenie i edytowanie kursu. Utwórz nowy kurs. Dział jest wybierany przez jego klucz podstawowy (liczbę całkowitą), a nie jego nazwę. Edytuj nowy kurs. Po zakończeniu testowania usuń nowy kurs.

Tworzenie klasy bazowej do współużytkowania wspólnego kodu

Strony Courses/Create i Courses/Edit (Kursy/Edytuj) wymagają listy nazw działów. Utwórz klasę bazową Pages/Courses/DepartmentNamePageModel.cshtml.cs dla stron Tworzenie i edytowanie:

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                        "DepartmentID", "Name", selectedDepartment);
        }
    }
}

Powyższy kod tworzy obiekt , SelectList aby zawierać listę nazw działów. Jeśli selectedDepartment zostanie określony, ten dział zostanie wybrany w elemecie SelectList.

Klasy modelu tworzenia i edytowania stron będą pochodzić z klasy DepartmentNamePageModel.

Dostosowywanie stron kursów

Po utworzeniu nowej jednostki kursu musi ona mieć relację z istniejącym działem. Aby dodać dział podczas tworzenia kursu, klasa podstawowa tworzenia i edytowania zawiera listę rozwijaną do wybierania działu. Lista rozwijana ustawia właściwość klucza obcego Course.DepartmentID (FK). EF Core używa klucza Course.DepartmentID FK do załadowania Department właściwości nawigacji.

Tworzenie kursu

Zaktualizuj model tworzenia strony przy użyciu następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

Powyższy kod ma następujące działanie:

  • Pochodzi z klasy DepartmentNamePageModel.
  • Używa TryUpdateModelAsync metody , aby zapobiec przesłonięć.
  • Zamienia wartość ViewData["DepartmentID"] na DepartmentNameSL (z klasy bazowej).

ViewData["DepartmentID"] element jest zastępowany silnie typizowane DepartmentNameSL. Silnie typizowane modele są preferowane w przypadku słabych typów. Aby uzyskać więcej informacji, zobacz Weakly typed data (ViewData and ViewBag).

Aktualizowanie strony Tworzenie kursów

Zaktualizuj Pages/Courses/Create.cshtml za pomocą następującego kodu:

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy znacznik wprowadza następujące zmiany:

  • Zmienia podpis z Identyfikator działu na Dział.
  • Zamienia wartość "ViewBag.DepartmentID" na DepartmentNameSL (z klasy bazowej).
  • Dodaje opcję "Wybierz dział". Ta zmiana powoduje renderowanie "Wybierz dział" zamiast pierwszego działu.
  • Dodaje komunikat weryfikacji, gdy dział nie jest wybrany.

Strona Razor używa pomocnika Wybierz tag:

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

Przetestuj stronę Tworzenie. Na stronie Tworzenie zostanie wyświetlona nazwa działu, a nie identyfikator działu.

Zaktualizuj stronę Edycja kursów.

Zastąp kod w pliku Pages/Courses/Edit.cshtml.cs następującym kodem:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

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

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context,Course.DepartmentID);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

Zmiany są podobne do tych wprowadzonych w modelu tworzenia strony. W poprzednim kodzie PopulateDepartmentsDropDownList przekazuje identyfikator działu, który wybierze dział określony na liście rozwijanej.

Zaktualizuj Pages/Courses/Edit.cshtml za pomocą następującego znacznika:

@page
@model ContosoUniversity.Pages.Courses.EditModel

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

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Powyższy znacznik wprowadza następujące zmiany:

  • Wyświetla identyfikator kursu. Ogólnie klucz podstawowy (PK) jednostki nie jest wyświetlany. Pakiety PKs są zwykle bez znaczenia dla użytkowników. W takim przypadku klucz PK jest numerem kursu.
  • Zmienia podpis z Identyfikator działu na Dział.
  • Zamienia wartość "ViewBag.DepartmentID" na DepartmentNameSL (z klasy bazowej).

Strona zawiera ukryte pole (<input type="hidden">) dla numeru kursu. Dodanie pomocnika asp-for="Course.CourseID" tagów <label> w programie nie eliminuje potrzeby pola ukrytego. <input type="hidden"> jest wymagany, aby numer kursu został uwzględniony w opublikowanych danych, gdy użytkownik kliknie przycisk Zapisz.

Przetestuj zaktualizowany kod. Tworzenie, edytowanie i usuwanie kursu.

Dodawanie funkcji AsNoTracking do modeli stron Szczegóły i Usuwanie

AsNoTracking może zwiększyć wydajność, gdy śledzenie nie jest wymagane. Dodaj AsNoTracking do modelu strony Usuń i Szczegóły. Poniższy kod przedstawia zaktualizowany model strony Usuń:

public class DeleteModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public DeleteModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Course Course { get; set; }

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

        Course = await _context.Courses
            .AsNoTracking()
            .Include(c => c.Department)
            .FirstOrDefaultAsync(m => m.CourseID == id);

        if (Course == null)
        {
            return NotFound();
        }
        return Page();
    }

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

        Course = await _context.Courses
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.CourseID == id);

        if (Course != null)
        {
            _context.Courses.Remove(Course);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage("./Index");
    }
}

Zaktualizuj metodę OnGetAsync Pages/Courses/Details.cshtml.cs w pliku:

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

    Course = await _context.Courses
         .AsNoTracking()
         .Include(c => c.Department)
         .FirstOrDefaultAsync(m => m.CourseID == id);

    if (Course == null)
    {
        return NotFound();
    }
    return Page();
}

Modyfikowanie stron Usuwanie i Szczegóły

Zaktualizuj stronę Usuń Razor przy użyciu następującego znacznika:

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

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

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Course</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Department.DepartmentID)
        </dd>
    </dl>
    
    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-default" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Wprowadź te same zmiany na stronie Szczegóły.

Testowanie stron kursu

Przetestuj tworzenie, edytowanie, szczegóły i usuwanie.

Aktualizowanie stron instruktora

Poniższe sekcje aktualizują strony instruktora.

Dodawanie lokalizacji biura

Podczas edytowania rekordu instruktora możesz zaktualizować przydział biura instruktora. Jednostka Instructor ma relację jeden do zera lub jednego z jednostką OfficeAssignment . Kod instruktora musi obsługiwać:

  • Jeśli użytkownik wyczyści przypisanie pakietu Office, usuń OfficeAssignment jednostkę.
  • Jeśli użytkownik wprowadzi przypisanie pakietu Office i był pusty, utwórz nową OfficeAssignment jednostkę.
  • Jeśli użytkownik zmieni przypisanie pakietu Office, zaktualizuj OfficeAssignment jednostkę.

Zaktualizuj model strony edycji instruktorów przy użyciu następującego kodu:

public class EditModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public EditModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Instructor Instructor { get; set; }

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

        Instructor = await _context.Instructors
            .Include(i => i.OfficeAssignment)
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.ID == id);

        if (Instructor == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

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

        if (await TryUpdateModelAsync<Instructor>(
            instructorToUpdate,
            "Instructor",
            i => i.FirstMidName, i => i.LastName, 
            i => i.HireDate, i => i.OfficeAssignment))
        {
            if (String.IsNullOrWhiteSpace(
                instructorToUpdate.OfficeAssignment?.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }
            await _context.SaveChangesAsync();
        }
        return RedirectToPage("./Index");

    }
}

Powyższy kod ma następujące działanie:

  • Pobiera bieżącą Instructor jednostkę z bazy danych przy użyciu chętnego OfficeAssignment ładowania dla właściwości nawigacji.
  • Aktualizuje pobraną Instructor jednostkę przy użyciu wartości z powiązania modelu. TryUpdateModel zapobiega przesłonięć.
  • Jeśli lokalizacja biura jest pusta, ustawia wartość Instructor.OfficeAssignment null. Gdy Instructor.OfficeAssignment ma wartość null, powiązany wiersz w OfficeAssignment tabeli zostanie usunięty.

Aktualizowanie strony edycji instruktora

Zaktualizuj Pages/Instructors/Edit.cshtml za pomocą lokalizacji biura:

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Sprawdź, czy możesz zmienić lokalizację biura instruktorów.

Dodawanie przypisań kursu do strony Edycja instruktora

Instruktorzy mogą uczyć dowolną liczbę kursów. W tej sekcji dodasz możliwość zmiany przypisań kursu. Na poniższej ilustracji przedstawiono zaktualizowaną stronę edycji instruktora:

Strona Edycji instruktora z kursami

Course i Instructor ma relację wiele do wielu. Aby dodawać i usuwać relacje, należy dodawać i usuwać jednostki z CourseAssignments zestawu jednostek sprzężenia.

Pola wyboru umożliwiają zmianę kursów, do których jest przypisany instruktor. Pole wyboru jest wyświetlane dla każdego kursu w bazie danych. Kursy przypisane przez instruktora są sprawdzane. 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 użyjesz innego interfejsu użytkownika do wyświetlania kursów.
  • Metoda manipulowania jednostką sprzężenia w celu utworzenia lub usunięcia relacji nie uległa zmianie.

Dodawanie zajęć do obsługi tworzenia i edytowania stron instruktora

Utwórz Models/SchoolViewModels/AssignedCourseData.cs za pomocą następującego kodu:

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

Klasa AssignedCourseData zawiera dane służące do tworzenia pól wyboru dla przypisanych kursów przez instruktora.

Utwórz klasę bazową Pages/Instructors/InstructorCoursesPageModel.cshtml.cs :

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {

        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context, 
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.CourseAssignments.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }

        public void UpdateInstructorCourses(SchoolContext context, 
            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
                                .SingleOrDefault(i => i.CourseID == course.CourseID);
                        context.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

Jest InstructorCoursesPageModel to klasa bazowa, która będzie używana dla modeli stron Edytowanie i tworzenie. PopulateAssignedCourseData odczytuje wszystkie Course jednostki w celu wypełnienia AssignedCourseDataList. Dla każdego kursu kod ustawia CourseIDtytuł , i czy instruktor jest przypisany do kursu. Zestaw skrótów służy do tworzenia wydajnych odnośników.

Instruktorzy Edytuj model strony

Zaktualizuj model strony edycji instruktora przy użyciu następującego kodu:

public class EditModel : InstructorCoursesPageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public EditModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Instructor Instructor { get; set; }

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

        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(_context, Instructor);
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id, string[] selectedCourses)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

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

        if (await TryUpdateModelAsync<Instructor>(
            instructorToUpdate,
            "Instructor",
            i => i.FirstMidName, i => i.LastName,
            i => i.HireDate, i => i.OfficeAssignment))
        {
            if (String.IsNullOrWhiteSpace(
                instructorToUpdate.OfficeAssignment?.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }
            UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
        UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
        PopulateAssignedCourseData(_context, instructorToUpdate);
        return Page();
    }
}

Powyższy kod obsługuje zmiany przypisania pakietu Office.

Zaktualizuj widok instruktora Razor :

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Uwaga

Podczas wklejania kodu w programie Visual Studio podziały wierszy są zmieniane w sposób, który przerywa kod. Naciśnij Ctrl+Z raz, aby cofnąć automatyczne formatowanie. Ctrl+Z naprawia podziały 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. Po wybraniu bloku nowego kodu naciśnij Tab trzy razy, aby ustawić nowy kod przy użyciu istniejącego kodu. Zagłosuj lub przejrzyj stan tej usterki za pomocą tego linku.

Powyższy kod tworzy tabelę HTML zawierającą trzy kolumny. Każda kolumna ma pole wyboru i podpis zawierający numer kursu i tytuł. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"). Użycie tej samej nazwy informuje powiązanie modelu, aby traktować je jako grupę. Atrybut wartości każdego pola wyboru jest ustawiony na CourseID. Po opublikowaniu strony powiązanie modelu przekazuje tablicę składającą się z CourseID wartości tylko zaznaczonych pól wyboru.

Gdy pola wyboru są początkowo renderowane, kursy przypisane do instruktora mają zaznaczone atrybuty.

Uruchom aplikację i przetestuj zaktualizowaną stronę edycji instruktorów. Zmień niektóre przydziały kursu. Zmiany są odzwierciedlane 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, inny interfejs użytkownika i inna metoda aktualizacji byłyby bardziej opłacalne i wydajne.

Aktualizowanie strony Tworzenie instruktorów

Zaktualizuj model strony Tworzenie instruktora przy użyciu następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.CourseAssignments = new List<CourseAssignment>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var newInstructor = new Instructor();
            if (selectedCourses != null)
            {
                newInstructor.CourseAssignments = new List<CourseAssignment>();
                foreach (var course in selectedCourses)
                {
                    var courseToAdd = new CourseAssignment
                    {
                        CourseID = int.Parse(course)
                    };
                    newInstructor.CourseAssignments.Add(courseToAdd);
                }
            }

            if (await TryUpdateModelAsync<Instructor>(
                newInstructor,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);                
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}

Powyższy kod jest podobny do Pages/Instructors/Edit.cshtml.cs kodu.

Zaktualizuj stronę Tworzenie Razor instruktora przy użyciu następującego znacznika:

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

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

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    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>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Przetestuj stronę Tworzenie instruktora.

Aktualizowanie strony Usuwanie

Zaktualizuj model strony Usuń przy użyciu następującego kodu:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

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

            Instructor = await _context.Instructors.SingleAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(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 RedirectToPage("./Index");
        }
    }
}

Powyższy kod wprowadza następujące zmiany:

  • Używa chętnego CourseAssignments ładowania dla właściwości nawigacji. CourseAssignments muszą być dołączone lub nie są usuwane po usunięciu instruktora. Aby uniknąć konieczności ich odczytywania, skonfiguruj 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.

Dodatkowe zasoby