Bagikan melalui


Tutorial: Memperbarui data terkait - ASP.NET MVC dengan EF Core

Dalam tutorial sebelumnya Anda menampilkan data terkait; dalam tutorial ini Anda akan memperbarui data terkait dengan memperbarui bidang kunci asing dan properti navigasi.

Ilustrasi berikut ini memperlihatkan beberapa halaman yang akan Anda kerjakan.

Course Edit page

Edit Instructor page

Di tutorial ini, Anda akan:

  • Mengkustomisasi halaman Kursus
  • Tambahkan halaman Edit Instruktur
  • Menambahkan kursus ke halaman Edit
  • Perbarui halaman Hapus
  • Menambahkan lokasi dan kursus office ke halaman Buat

Prasyarat

Mengkustomisasi halaman Kursus

Ketika entitas baru Course dibuat, entitas harus memiliki hubungan dengan departemen yang ada. Untuk memfasilitasi hal ini, kode perancah mencakup metode pengontrol dan Membuat dan Mengedit tampilan yang menyertakan daftar drop-down untuk memilih departemen. Daftar drop-down mengatur Course.DepartmentID properti kunci asing, dan itu semua yang dibutuhkan Kerangka Kerja Entitas untuk memuat Department properti navigasi dengan entitas yang sesuai Department . Anda akan menggunakan kode perancah, tetapi mengubahnya sedikit untuk menambahkan penanganan kesalahan dan mengurutkan daftar drop-down.

Di CoursesController.cs, hapus empat metode Buat dan Edit dan ganti dengan kode berikut:

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

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

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

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

Edit Setelah metode HttpPost, buat metode baru yang memuat info departemen untuk daftar drop-down.

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

Metode ini PopulateDepartmentsDropDownList mendapatkan daftar semua departemen yang diurutkan menurut nama, membuat SelectList koleksi untuk daftar drop-down, dan meneruskan koleksi ke tampilan di ViewBag. Metode ini menerima parameter opsional selectedDepartment yang memungkinkan kode panggilan menentukan item yang akan dipilih saat daftar drop-down dirender. Tampilan akan meneruskan nama "DepartmentID" ke <select> pembantu tag, dan pembantu kemudian tahu untuk melihat ViewBag objek untuk SelectList bernama "DepartmentID".

Metode HttpGet Create memanggil PopulateDepartmentsDropDownList metode tanpa mengatur item yang dipilih, karena untuk kursus baru departemen belum ditetapkan:

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

Metode HttpGet Edit mengatur item yang dipilih, berdasarkan ID departemen yang sudah ditetapkan ke kursus yang sedang diedit:

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

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

Metode HttpPost untuk keduanya Create dan Edit juga menyertakan kode yang mengatur item yang dipilih saat mereka memutar ulang halaman setelah kesalahan. Ini memastikan bahwa ketika halaman diputar ulang untuk menampilkan pesan kesalahan, departemen apa pun yang dipilih tetap dipilih.

Menambahkan. AsNoTracking ke Metode Detail dan Hapus

Untuk mengoptimalkan performa halaman Detail Kursus dan Hapus, tambahkan AsNoTracking panggilan dalam Details metode HttpGet Delete dan .

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

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

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

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

    return View(course);
}

Mengubah tampilan Kursus

Di Views/Courses/Create.cshtml, tambahkan opsi "Pilih Departemen" ke daftar drop-down Departemen , ubah keterangan dari DepartmentID ke Departemen, dan tambahkan pesan validasi.

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

Dalam Views/Courses/Edit.cshtml, buat perubahan yang sama untuk bidang Departemen yang baru saja Anda lakukan di Create.cshtml.

Juga di Views/Courses/Edit.cshtml, tambahkan bidang nomor kursus sebelum bidang Judul . Karena nomor kursus adalah kunci utama, itu ditampilkan, tetapi tidak dapat diubah.

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

Sudah ada bidang tersembunyi (<input type="hidden">) untuk nomor kursus dalam tampilan Edit. Menambahkan pembantu <label> tag tidak menghilangkan kebutuhan akan bidang tersembunyi karena tidak menyebabkan nomor kursus disertakan dalam data yang diposting saat pengguna mengklik Simpan di halaman Edit .

Di Views/Courses/Delete.cshtml, tambahkan bidang nomor kursus di bagian atas dan ubah ID departemen menjadi nama departemen.

@model ContosoUniversity.Models.Course

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

<h2>Delete</h2>

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

Dalam Views/Courses/Details.cshtml, buat perubahan yang sama yang baru saja Anda lakukan untuk Delete.cshtml.

Menguji halaman Kursus

Jalankan aplikasi, pilih tab Kursus , klik Buat Baru, dan masukkan data untuk kursus baru:

Course Create page

Klik Buat. Halaman Indeks Kursus ditampilkan dengan kursus baru ditambahkan ke daftar. Nama departemen dalam daftar halaman Indeks berasal dari properti navigasi, memperlihatkan bahwa hubungan dibuat dengan benar.

Klik Edit pada kursus di halaman Indeks Kursus.

Course Edit page

Ubah data di halaman dan klik Simpan. Halaman Indeks Kursus ditampilkan dengan data kursus yang diperbarui.

Tambahkan halaman Edit Instruktur

Saat mengedit catatan instruktur, Anda ingin dapat memperbarui penetapan kantor instruktur. Entitas Instructor memiliki hubungan satu-ke-nol-atau-satu dengan OfficeAssignment entitas, yang berarti kode Anda harus menangani situasi berikut:

  • Jika pengguna menghapus penetapan office dan awalnya memiliki nilai, hapus OfficeAssignment entitas.

  • Jika pengguna memasukkan nilai penetapan office dan awalnya kosong, buat entitas baru OfficeAssignment .

  • Jika pengguna mengubah nilai penetapan office, ubah nilai di entitas yang sudah ada OfficeAssignment .

Memperbarui pengontrol Instruktur

Di InstructorsController.cs, ubah kode dalam metode HttpGet Edit sehingga memuat properti navigasi dan panggilan AsNoTrackingentitas OfficeAssignment Instruktur :

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

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

Ganti metode HttpPost Edit dengan kode berikut untuk menangani pembaruan penetapan office:

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

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

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

Kode melakukan hal berikut:

  • Mengubah nama metode menjadi EditPost karena tanda tangan sekarang sama dengan metode HttpGet Edit ( ActionName atribut menentukan bahwa /Edit/ URL masih digunakan).

  • Mendapatkan entitas saat ini Instructor dari database menggunakan pemuatan bersemangat untuk OfficeAssignment properti navigasi. Ini sama dengan apa yang Anda lakukan dalam metode HttpGet Edit .

  • Memperbarui entitas yang diambil Instructor dengan nilai dari pengikat model. Kelebihan TryUpdateModel beban memungkinkan Anda mendeklarasikan properti yang ingin Anda sertakan. Ini mencegah pengiriman berlebihan, seperti yang dijelaskan dalam tutorial kedua.

    if (await TryUpdateModelAsync<Instructor>(
        instructorToUpdate,
        "",
        i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
    
  • Jika lokasi kantor kosong, atur Instructor.OfficeAssignment properti ke null sehingga baris terkait dalam OfficeAssignment tabel akan dihapus.

    if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
    {
        instructorToUpdate.OfficeAssignment = null;
    }
    
  • Menyimpan perubahan ke database.

Memperbarui tampilan Edit Instruktur

Di Views/Instructors/Edit.cshtml, tambahkan bidang baru untuk mengedit lokasi kantor, di akhir sebelum tombol Simpan :

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

Jalankan aplikasi, pilih tab Instruktur , lalu klik Edit pada instruktur. Ubah Lokasi Office dan klik Simpan.

Instructor Edit page

Menambahkan kursus ke halaman Edit

Instruktur dapat mengajarkan sejumlah kursus. Sekarang Anda akan meningkatkan halaman Edit Instruktur dengan menambahkan kemampuan untuk mengubah penetapan kursus menggunakan sekelompok kotak centang, seperti yang ditunjukkan dalam cuplikan layar berikut:

Instructor Edit page with courses

Hubungan antara Course entitas dan Instructor adalah banyak-ke-banyak. Untuk menambahkan dan menghapus hubungan, Anda menambahkan dan menghapus entitas ke dan dari CourseAssignments kumpulan entitas gabungan.

UI yang memungkinkan Anda mengubah kursus mana yang ditetapkan instruktur adalah sekelompok kotak centang. Kotak centang untuk setiap kursus dalam database ditampilkan, dan kotak centang yang saat ini ditetapkan instruktur dipilih. Pengguna dapat memilih atau menghapus kotak centang untuk mengubah penetapan kursus. Jika jumlah kursus jauh lebih besar, Anda mungkin ingin menggunakan metode yang berbeda untuk menyajikan data dalam tampilan, tetapi Anda akan menggunakan metode yang sama untuk memanipulasi entitas gabungan untuk membuat atau menghapus hubungan.

Memperbarui pengontrol Instruktur

Untuk menyediakan data ke tampilan untuk daftar kotak centang, Anda akan menggunakan kelas model tampilan.

Buat AssignedCourseData.cs di folder SchoolViewModels dan ganti kode yang ada dengan kode berikut:

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

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

Di InstructorsController.cs, ganti metode HttpGet Edit dengan kode berikut. Perubahan disorot.

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

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

private void PopulateAssignedCourseData(Instructor instructor)
{
    var allCourses = _context.Courses;
    var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
    var viewModel = new List<AssignedCourseData>();
    foreach (var course in allCourses)
    {
        viewModel.Add(new AssignedCourseData
        {
            CourseID = course.CourseID,
            Title = course.Title,
            Assigned = instructorCourses.Contains(course.CourseID)
        });
    }
    ViewData["Courses"] = viewModel;
}

Kode menambahkan pemuatan bersemangat untuk Courses properti navigasi dan memanggil metode baru PopulateAssignedCourseData untuk memberikan informasi untuk array kotak centang menggunakan AssignedCourseData kelas model tampilan.

Kode dalam PopulateAssignedCourseData metode membaca semua Course entitas untuk memuat daftar kursus menggunakan kelas model tampilan. Untuk setiap kursus, kode memeriksa apakah kursus ada di properti navigasi instruktur Courses . Untuk membuat pencarian yang efisien saat memeriksa apakah kursus ditetapkan ke instruktur, kursus yang ditetapkan ke instruktur dimasukkan ke dalam HashSet koleksi. Properti Assigned diatur ke true untuk kursus yang ditetapkan instruktur. Tampilan akan menggunakan properti ini untuk menentukan kotak centang mana yang harus ditampilkan seperti yang dipilih. Akhirnya, daftar diteruskan ke tampilan di ViewData.

Selanjutnya, tambahkan kode yang dijalankan saat pengguna mengklik Simpan. EditPost Ganti metode dengan kode berikut, dan tambahkan metode baru yang memperbarui Courses properti navigasi entitas Instruktur.

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

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

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

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

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

Tanda tangan metode sekarang berbeda dari metode HttpGet Edit , sehingga nama metode berubah dari EditPost kembali ke Edit.

Karena tampilan tidak memiliki kumpulan entitas Kursus, pengikat model tidak dapat memperbarui CourseAssignments properti navigasi secara otomatis. Alih-alih menggunakan pengikat model untuk memperbarui CourseAssignments properti navigasi, Anda melakukannya dalam metode baru UpdateInstructorCourses . Oleh karena itu, Anda perlu mengecualikan CourseAssignments properti dari pengikatan model. Ini tidak memerlukan perubahan apa pun pada kode yang memanggil TryUpdateModel karena Anda menggunakan kelebihan beban yang memerlukan persetujuan eksplisit dan CourseAssignments tidak ada dalam daftar sertakan.

Jika tidak ada kotak centang yang dipilih, kode dalam UpdateInstructorCourses menginisialisasi CourseAssignments properti navigasi dengan koleksi kosong dan mengembalikan:

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

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

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

Kode kemudian mengulangi semua kursus dalam database dan memeriksa setiap kursus terhadap yang saat ini ditetapkan ke instruktur versus yang dipilih dalam tampilan. Untuk memfasilitasi pencarian yang efisien, dua koleksi terakhir disimpan dalam HashSet objek.

Jika kotak centang untuk kursus dipilih tetapi kursus tidak ada di Instructor.CourseAssignments properti navigasi, kursus ditambahkan ke koleksi di properti navigasi.

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

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

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

Jika kotak centang untuk kursus tidak dipilih, tetapi kursus berada di Instructor.CourseAssignments properti navigasi, kursus akan dihapus dari properti navigasi.

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

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

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

Memperbarui tampilan Instruktur

Di Views/Instructors/Edit.cshtml, tambahkan bidang Kursus dengan array kotak centang dengan menambahkan kode berikut segera setelah div elemen untuk bidang Office dan sebelum div elemen untuk tombol Simpan .

Catatan

Saat Anda menempelkan kode di Visual Studio, hentian baris dapat diubah dengan cara yang merusak kode. Jika kode terlihat berbeda setelah menempelkan, tekan Ctrl+Z satu kali untuk membatalkan pemformatan otomatis. Ini akan memperbaiki jeda baris sehingga terlihat seperti apa yang Anda lihat di sini. Indentasi tidak harus sempurna, tetapi @:</tr><tr>baris , , @:<td>@:</td>, dan @:</tr> masing-masing harus berada di satu baris seperti yang ditunjukkan atau Anda akan mendapatkan kesalahan runtime. Dengan blok kode baru dipilih, tekan Tab tiga kali untuk mengjajarkan kode baru dengan kode yang ada. Masalah ini diperbaiki di Visual Studio 2019.

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                                   name="selectedCourses"
                                   value="@course.CourseID"
                                   @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                                   @course.CourseID @:  @course.Title
                        @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

Kode ini membuat tabel HTML yang memiliki tiga kolom. Di setiap kolom adalah kotak centang diikuti dengan keterangan yang terdiri dari nomor kursus dan judul. Semua kotak centang memiliki nama yang sama ("selectedCourses"), yang menginformasikan binder model bahwa mereka akan diperlakukan sebagai grup. Atribut nilai dari setiap kotak centang diatur ke nilai CourseID. Saat halaman diposting, pengikat model meneruskan array ke pengontrol yang terdiri dari CourseID nilai hanya untuk kotak centang yang dipilih.

Ketika kotak centang awalnya dirender, kotak centang yang untuk kursus yang ditetapkan ke instruktur telah memeriksa atribut, yang memilihnya (menampilkannya dicentang).

Jalankan aplikasi, pilih tab Instruktur , dan klik Edit pada instruktur untuk melihat halaman Edit .

Instructor Edit page with courses

Ubah beberapa tugas kursus dan klik Simpan. Perubahan yang Anda buat tercermin pada halaman Indeks.

Catatan

Pendekatan yang diambil di sini untuk mengedit data kursus instruktur berfungsi dengan baik ketika ada sejumlah kursus yang terbatas. Untuk koleksi yang jauh lebih besar, UI yang berbeda dan metode pembaruan yang berbeda akan diperlukan.

Perbarui halaman Hapus

Di InstructorsController.cs, hapus DeleteConfirmed metode dan sisipkan kode berikut di tempatnya.

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

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

    _context.Instructors.Remove(instructor);

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

Pembaruan kode ini membuat perubahan berikut:

  • Apakah pemuatan bersemangat CourseAssignments untuk properti navigasi. Anda harus menyertakan ini atau EF tidak akan tahu tentang entitas terkait CourseAssignment dan tidak akan menghapusnya. Untuk menghindari perlu membacanya di sini, Anda dapat mengonfigurasi penghapusan kaskade dalam database.

  • Jika instruktur yang akan dihapus ditetapkan sebagai administrator departemen mana pun, menghapus penugasan instruktur dari departemen tersebut.

Menambahkan lokasi dan kursus office ke halaman Buat

Di InstructorsController.cs, hapus metode HttpGet dan HttpPost Create , lalu tambahkan kode berikut di tempatnya:

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

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

Kode ini mirip dengan apa yang Anda lihat untuk Edit metode kecuali bahwa awalnya tidak ada kursus yang dipilih. Metode HttpGet Create memanggil PopulateAssignedCourseData metode bukan karena mungkin ada kursus yang dipilih tetapi untuk menyediakan koleksi kosong untuk foreach perulangan dalam tampilan (jika tidak, kode tampilan akan melemparkan pengecualian referensi null).

Metode HttpPost Create menambahkan setiap kursus yang dipilih ke CourseAssignments properti navigasi sebelum memeriksa kesalahan validasi dan menambahkan instruktur baru ke database. Kursus ditambahkan bahkan jika ada kesalahan model sehingga ketika ada kesalahan model (misalnya, pengguna memasukkan tanggal yang tidak valid), dan halaman diputar ulang dengan pesan kesalahan, pilihan kursus apa pun yang dibuat dipulihkan secara otomatis.

Perhatikan bahwa untuk dapat menambahkan kursus ke CourseAssignments properti navigasi, Anda harus menginisialisasi properti sebagai koleksi kosong:

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

Sebagai alternatif untuk melakukan ini dalam kode pengontrol, Anda dapat melakukannya dalam Instructor model dengan mengubah getter properti untuk membuat koleksi secara otomatis jika tidak ada, seperti yang ditunjukkan dalam contoh berikut:

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

Jika Anda mengubah properti dengan CourseAssignments cara ini, Anda dapat menghapus kode inisialisasi properti eksplisit di pengontrol.

Di Views/Instructor/Create.cshtml, tambahkan kotak teks lokasi office dan kotak centang untuk kursus sebelum tombol Kirim. Seperti dalam kasus halaman Edit, perbaiki pemformatan jika Visual Studio memformat ulang kode saat Anda menempelkannya.

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

<div class="form-group">
    <div class="col-md-offset-2 col-md-10">
        <table>
            <tr>
                @{
                    int cnt = 0;
                    List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;

                    foreach (var course in courses)
                    {
                        if (cnt++ % 3 == 0)
                        {
                            @:</tr><tr>
                        }
                        @:<td>
                            <input type="checkbox"
                                   name="selectedCourses"
                                   value="@course.CourseID"
                                   @(Html.Raw(course.Assigned ? "checked=\"checked\"" : "")) />
                                   @course.CourseID @:  @course.Title
                            @:</td>
                    }
                    @:</tr>
                }
        </table>
    </div>
</div>

Uji dengan menjalankan aplikasi dan membuat instruktur.

Menangani Transaksi

Seperti yang dijelaskan dalam tutorial CRUD, Kerangka Kerja Entitas secara implisit menerapkan transaksi. Untuk skenario di mana Anda memerlukan lebih banyak kontrol -- misalnya, jika Anda ingin menyertakan operasi yang dilakukan di luar Kerangka Kerja Entitas dalam transaksi -- lihat Transaksi.

Mendapatkan kode

Unduh atau lihat aplikasi yang telah selesai.

Langkah berikutnya

Di tutorial ini, Anda akan:

  • Halaman Kursus yang Dikustomisasi
  • Menambahkan halaman Edit Instruktur
  • Menambahkan kursus ke halaman Edit
  • Halaman Hapus yang Diperbarui
  • Menambahkan lokasi dan kursus office ke halaman Buat

Lanjutkan ke tutorial berikutnya untuk mempelajari cara menangani konflik konkurensi.