Bagikan melalui


Bagian 2, Razor Halaman dengan EF Core inti ASP.NET - CRUD

Oleh Tom Dykstra, Jeremy Likness, dan Jon P Smith

Aplikasi web Contoso University menunjukkan cara membuat Razor aplikasi web Pages menggunakan EF Core dan Visual Studio. Untuk informasi tentang seri tutorial, lihat tutorial pertama.

Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai dan bandingkan kode tersebut dengan apa yang Anda buat dengan mengikuti tutorial.

Dalam tutorial ini, kode CRUD (buat, baca, perbarui, hapus) perancah ditinjau dan disesuaikan.

Tidak ada repositori

Beberapa pengembang menggunakan lapisan layanan atau pola repositori untuk membuat lapisan abstraksi antara UI (Razor Pages) dan lapisan akses data. Tutorial ini tidak melakukan itu. Untuk meminimalkan kompleksitas dan menjaga tutorial tetap fokus pada EF Core, EF Core kode ditambahkan langsung ke kelas model halaman.

Memperbarui halaman Detail

Kode perancah untuk halaman Siswa tidak menyertakan data pendaftaran. Di bagian ini, pendaftaran ditambahkan ke Details halaman.

Membaca pendaftaran

Untuk menampilkan data pendaftaran siswa di halaman, data pendaftaran harus dibaca. Kode perancah dalam Pages/Students/Details.cshtml.cs hanya Student membaca data, tanpa Enrollment data:

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

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

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

OnGetAsync Ganti metode dengan kode berikut untuk membaca data pendaftaran untuk siswa yang dipilih. Perubahan disorot.

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

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

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

Metode Include dan ThenInclude menyebabkan konteks memuat Student.Enrollments properti navigasi, dan dalam setiap pendaftaran Enrollment.Course properti navigasi. Metode ini diperiksa secara rinci dalam tutorial Membaca data terkait.

Metode ini AsNoTracking meningkatkan performa dalam skenario di mana entitas yang dikembalikan tidak diperbarui dalam konteks saat ini. AsNoTracking dibahas nanti dalam tutorial ini.

Tampilkan pendaftaran

Ganti kode dengan Pages/Students/Details.cshtml kode berikut untuk menampilkan daftar pendaftaran. Perubahan disorot.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

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

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Kode sebelumnya mengulangi entitas di Enrollments properti navigasi. Untuk setiap pendaftaran, ini menampilkan judul kursus dan nilai. Judul kursus diambil dari Course entitas yang disimpan di Course properti navigasi entitas Pendaftaran.

Jalankan aplikasi, pilih tab Siswa , dan klik tautan Detail untuk siswa. Daftar kursus dan nilai untuk siswa yang dipilih ditampilkan.

Cara membaca satu entitas

Kode yang dihasilkan menggunakan FirstOrDefaultAsync untuk membaca satu entitas. Metode ini mengembalikan null jika tidak ada yang ditemukan; jika tidak, ini mengembalikan baris pertama yang ditemukan yang memenuhi kriteria filter kueri. FirstOrDefaultAsync umumnya adalah pilihan yang lebih baik daripada alternatif berikut:

  • SingleOrDefaultAsync - Melemparkan pengecualian jika ada lebih dari satu entitas yang memenuhi filter kueri. Untuk menentukan apakah lebih dari satu baris dapat dikembalikan oleh kueri, SingleOrDefaultAsync coba ambil beberapa baris. Pekerjaan tambahan ini tidak perlu jika kueri hanya dapat mengembalikan satu entitas, seperti ketika mencari pada kunci unik.
  • FindAsync - Menemukan entitas dengan kunci primer (PK). Jika entitas dengan PK sedang dilacak oleh konteks, entitas dikembalikan tanpa permintaan ke database. Metode ini dioptimalkan untuk mencari satu entitas, tetapi Anda tidak dapat memanggil Include dengan FindAsync. Jadi jika data terkait diperlukan, FirstOrDefaultAsync adalah pilihan yang lebih baik.

Merutekan data vs. string kueri

URL untuk halaman Detail adalah https://localhost:<port>/Students/Details?id=1. Nilai kunci utama entitas berada dalam string kueri. Beberapa pengembang lebih suka meneruskan nilai kunci dalam data rute: https://localhost:<port>/Students/Details/1. Untuk informasi selengkapnya, lihat Memperbarui kode yang dihasilkan.

Memperbarui halaman Buat

Kode perancah OnPostAsync untuk halaman Buat rentan terhadap overposting. OnPostAsync Ganti metode dengan Pages/Students/Create.cshtml.cs kode berikut.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Kode sebelumnya membuat objek Siswa lalu menggunakan bidang formulir yang diposting untuk memperbarui properti objek Siswa. Metode TryUpdateModelAsync :

  • Menggunakan nilai formulir yang diposting dari PageContext properti di PageModel.
  • Memperbarui hanya properti yang tercantum (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Mencari bidang formulir dengan awalan "siswa". Misalnya, Student.FirstMidName. Ini tidak peka huruf besar/kecil.
  • Menggunakan sistem pengikatan model untuk mengonversi nilai formulir dari string ke jenis dalam Student model. Misalnya, EnrollmentDate dikonversi ke DateTime.

Jalankan aplikasi, dan buat entitas siswa untuk menguji halaman Buat.

Overposting

Menggunakan TryUpdateModel untuk memperbarui bidang dengan nilai yang diposting adalah praktik terbaik keamanan karena mencegah overposting. Misalnya, entitas Siswa menyertakan Secret properti yang tidak boleh diperbarui atau ditambahkan halaman web ini:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Bahkan jika aplikasi tidak memiliki Secret bidang di Halaman buat atau perbarui Razor , peretas dapat mengatur nilai dengan Secret melakukan overposting. Peretas dapat menggunakan alat seperti Fiddler, atau menulis beberapa JavaScript, untuk memposting Secret nilai formulir. Kode asli tidak membatasi bidang yang digunakan pengikat model saat membuat instans Siswa.

Nilai apa pun yang ditentukan peretas untuk Secret bidang formulir diperbarui dalam database. Gambar berikut menunjukkan alat Fiddler yang menambahkan Secret bidang, dengan nilai "OverPost", ke nilai formulir yang diposting.

Fiddler adding Secret field

Nilai "OverPost" berhasil ditambahkan ke Secret properti baris yang disisipkan. Itu terjadi meskipun perancang aplikasi tidak pernah menginginkan Secret properti diatur dengan halaman Buat.

Lihat model

Model tampilan menyediakan cara alternatif untuk mencegah overposting.

Model aplikasi sering disebut model domain. Model domain biasanya berisi semua properti yang diperlukan oleh entitas yang sesuai dalam database. Model tampilan hanya berisi properti yang diperlukan untuk halaman UI, misalnya, halaman Buat.

Selain model tampilan, beberapa aplikasi menggunakan model pengikatan atau model input untuk meneruskan data antara Razor kelas model halaman Halaman dan browser.

Pertimbangkan model tampilan berikut StudentVM :

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Kode berikut menggunakan StudentVM model tampilan untuk membuat siswa baru:

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

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

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Metode SetValues mengatur nilai objek ini dengan membaca nilai dari objek lain PropertyValues . SetValues menggunakan pencocokan nama properti. Jenis model tampilan:

  • Tidak perlu terkait dengan jenis model.
  • Harus memiliki properti yang cocok.

Menggunakan StudentVM memerlukan penggunaan StudentVM halaman Buat daripada Student:

@page
@model CreateVMModel

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

<h1>Create</h1>

<h4>Student</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="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </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");}
}

Memperbarui halaman Edit

Di Pages/Students/Edit.cshtml.cs, ganti OnGetAsync metode dan OnPostAsync dengan kode berikut.

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

    Student = await _context.Students.FindAsync(id);

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

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

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

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Perubahan kode mirip dengan halaman Buat dengan beberapa pengecualian:

  • FirstOrDefaultAsync telah diganti dengan FindAsync. Ketika Anda tidak perlu menyertakan data terkait, FindAsync lebih efisien.
  • OnPostAsync memiliki id parameter .
  • Siswa saat ini diambil dari database, daripada membuat siswa kosong.

Jalankan aplikasi, dan uji dengan membuat dan mengedit siswa.

Status Entitas

Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database. Informasi pelacakan ini menentukan apa yang terjadi ketika SaveChangesAsync dipanggil. Misalnya, ketika entitas baru diteruskan ke AddAsync metode , status entitas tersebut diatur ke Added. Ketika SaveChangesAsync dipanggil, konteks database mengeluarkan perintah SQL INSERT .

Entitas mungkin berada di salah satu status berikut:

  • Added: Entitas belum ada di database. Metode mengeluarkan SaveChangesINSERT pernyataan.

  • Unchanged: Tidak ada perubahan yang perlu disimpan dengan entitas ini. Entitas memiliki status ini saat dibaca dari database.

  • Modified: Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode mengeluarkan SaveChangesUPDATE pernyataan.

  • Deleted: Entitas telah ditandai untuk penghapusan. Metode mengeluarkan SaveChangesDELETE pernyataan.

  • Detached: Entitas tidak dilacak oleh konteks database.

Di aplikasi desktop, perubahan status biasanya diatur secara otomatis. Entitas dibaca, perubahan dilakukan, dan status entitas secara otomatis diubah menjadi Modified. SaveChanges Panggilan menghasilkan pernyataan SQL UPDATE yang hanya memperbarui properti yang diubah.

Di aplikasi web, DbContext yang membaca entitas dan menampilkan data dibuang setelah halaman dirender. Ketika metode halaman OnPostAsync dipanggil, permintaan web baru dibuat dan dengan instans baru dari DbContext. Pembacaan ulang entitas dalam konteks baru mensimulasikan pemrosesan desktop.

Memperbarui halaman Hapus

Di bagian ini, pesan kesalahan kustom diimplementasikan saat panggilan gagal SaveChanges .

Ganti kode di Pages/Students/Delete.cshtml.cs dengan kode berikut:

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

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<DeleteModel> _logger;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context,
                           ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

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

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

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

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
            }

            return Page();
        }

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

            var student = await _context.Students.FindAsync(id);

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

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, ErrorMessage);

                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Kode sebelumnya:

  • Menambahkan Pengelogan.
  • Menambahkan parameter saveChangesError opsional ke OnGetAsync tanda tangan metode. saveChangesError menunjukkan apakah metode dipanggil setelah kegagalan untuk menghapus objek siswa.

Operasi penghapusan mungkin gagal karena masalah jaringan sementara. Kesalahan jaringan sementara lebih mungkin terjadi ketika database berada di cloud. Parameternya saveChangesError adalah false ketika halaman OnGetAsync Hapus dipanggil dari UI. Ketika OnGetAsync dipanggil oleh OnPostAsync karena operasi penghapusan gagal, saveChangesError parameternya adalah true.

Metode mengambil OnPostAsync entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted. Ketika SaveChanges dipanggil, perintah SQL DELETE dihasilkan. Jika Remove gagal:

  • Pengecualian database tertangkap.
  • Metode Hapus halaman OnGetAsync dipanggil dengan saveChangesError=true.

Tambahkan pesan kesalahan ke Pages/Students/Delete.cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

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

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Jalankan aplikasi dan hapus siswa untuk menguji halaman Hapus.

Langkah berikutnya

Dalam tutorial ini, kode CRUD (buat, baca, perbarui, hapus) perancah ditinjau dan disesuaikan.

Tidak ada repositori

Beberapa pengembang menggunakan lapisan layanan atau pola repositori untuk membuat lapisan abstraksi antara UI (Razor Pages) dan lapisan akses data. Tutorial ini tidak melakukan itu. Untuk meminimalkan kompleksitas dan menjaga tutorial tetap fokus pada EF Core, EF Core kode ditambahkan langsung ke kelas model halaman.

Memperbarui halaman Detail

Kode perancah untuk halaman Siswa tidak menyertakan data pendaftaran. Di bagian ini, pendaftaran ditambahkan ke Details halaman.

Membaca pendaftaran

Untuk menampilkan data pendaftaran siswa di halaman, data pendaftaran harus dibaca. Kode perancah dalam Pages/Students/Details.cshtml.cs hanya Student membaca data, tanpa Enrollment data:

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

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

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

OnGetAsync Ganti metode dengan kode berikut untuk membaca data pendaftaran untuk siswa yang dipilih. Perubahan disorot.

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

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

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

Metode Include dan ThenInclude menyebabkan konteks memuat Student.Enrollments properti navigasi, dan dalam setiap pendaftaran Enrollment.Course properti navigasi. Metode ini diperiksa secara rinci dalam tutorial Membaca data terkait.

Metode ini AsNoTracking meningkatkan performa dalam skenario di mana entitas yang dikembalikan tidak diperbarui dalam konteks saat ini. AsNoTracking dibahas nanti dalam tutorial ini.

Tampilkan pendaftaran

Ganti kode dengan Pages/Students/Details.cshtml kode berikut untuk menampilkan daftar pendaftaran. Perubahan disorot.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

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

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Kode sebelumnya mengulangi entitas di Enrollments properti navigasi. Untuk setiap pendaftaran, ini menampilkan judul kursus dan nilai. Judul kursus diambil dari Course entitas yang disimpan di Course properti navigasi entitas Pendaftaran.

Jalankan aplikasi, pilih tab Siswa , dan klik tautan Detail untuk siswa. Daftar kursus dan nilai untuk siswa yang dipilih ditampilkan.

Cara membaca satu entitas

Kode yang dihasilkan menggunakan FirstOrDefaultAsync untuk membaca satu entitas. Metode ini mengembalikan null jika tidak ada yang ditemukan; jika tidak, ini mengembalikan baris pertama yang ditemukan yang memenuhi kriteria filter kueri. FirstOrDefaultAsync umumnya adalah pilihan yang lebih baik daripada alternatif berikut:

  • SingleOrDefaultAsync - Melemparkan pengecualian jika ada lebih dari satu entitas yang memenuhi filter kueri. Untuk menentukan apakah lebih dari satu baris dapat dikembalikan oleh kueri, SingleOrDefaultAsync coba ambil beberapa baris. Pekerjaan tambahan ini tidak perlu jika kueri hanya dapat mengembalikan satu entitas, seperti ketika mencari pada kunci unik.
  • FindAsync - Menemukan entitas dengan kunci primer (PK). Jika entitas dengan PK sedang dilacak oleh konteks, entitas dikembalikan tanpa permintaan ke database. Metode ini dioptimalkan untuk mencari satu entitas, tetapi Anda tidak dapat memanggil Include dengan FindAsync. Jadi jika data terkait diperlukan, FirstOrDefaultAsync adalah pilihan yang lebih baik.

Merutekan data vs. string kueri

URL untuk halaman Detail adalah https://localhost:<port>/Students/Details?id=1. Nilai kunci utama entitas berada dalam string kueri. Beberapa pengembang lebih suka meneruskan nilai kunci dalam data rute: https://localhost:<port>/Students/Details/1. Untuk informasi selengkapnya, lihat Memperbarui kode yang dihasilkan.

Memperbarui halaman Buat

Kode perancah OnPostAsync untuk halaman Buat rentan terhadap overposting. OnPostAsync Ganti metode dengan Pages/Students/Create.cshtml.cs kode berikut.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Kode sebelumnya membuat objek Siswa lalu menggunakan bidang formulir yang diposting untuk memperbarui properti objek Siswa. Metode TryUpdateModelAsync :

  • Menggunakan nilai formulir yang diposting dari PageContext properti di PageModel.
  • Memperbarui hanya properti yang tercantum (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Mencari bidang formulir dengan awalan "siswa". Misalnya, Student.FirstMidName. Ini tidak peka huruf besar/kecil.
  • Menggunakan sistem pengikatan model untuk mengonversi nilai formulir dari string ke jenis dalam Student model. Misalnya, EnrollmentDate dikonversi ke DateTime.

Jalankan aplikasi, dan buat entitas siswa untuk menguji halaman Buat.

Overposting

Menggunakan TryUpdateModel untuk memperbarui bidang dengan nilai yang diposting adalah praktik terbaik keamanan karena mencegah overposting. Misalnya, entitas Siswa menyertakan Secret properti yang tidak boleh diperbarui atau ditambahkan halaman web ini:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Bahkan jika aplikasi tidak memiliki Secret bidang di Halaman buat atau perbarui Razor , peretas dapat mengatur nilai dengan Secret melakukan overposting. Peretas dapat menggunakan alat seperti Fiddler, atau menulis beberapa JavaScript, untuk memposting Secret nilai formulir. Kode asli tidak membatasi bidang yang digunakan pengikat model saat membuat instans Siswa.

Nilai apa pun yang ditentukan peretas untuk Secret bidang formulir diperbarui dalam database. Gambar berikut menunjukkan alat Fiddler yang menambahkan Secret bidang, dengan nilai "OverPost", ke nilai formulir yang diposting.

Fiddler adding Secret field

Nilai "OverPost" berhasil ditambahkan ke Secret properti baris yang disisipkan. Itu terjadi meskipun perancang aplikasi tidak pernah menginginkan Secret properti diatur dengan halaman Buat.

Lihat model

Model tampilan menyediakan cara alternatif untuk mencegah overposting.

Model aplikasi sering disebut model domain. Model domain biasanya berisi semua properti yang diperlukan oleh entitas yang sesuai dalam database. Model tampilan hanya berisi properti yang diperlukan untuk halaman UI, misalnya, halaman Buat.

Selain model tampilan, beberapa aplikasi menggunakan model pengikatan atau model input untuk meneruskan data antara Razor kelas model halaman Halaman dan browser.

Pertimbangkan model tampilan berikut StudentVM :

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Kode berikut menggunakan StudentVM model tampilan untuk membuat siswa baru:

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

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

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Metode SetValues mengatur nilai objek ini dengan membaca nilai dari objek lain PropertyValues . SetValues menggunakan pencocokan nama properti. Jenis model tampilan:

  • Tidak perlu terkait dengan jenis model.
  • Harus memiliki properti yang cocok.

Menggunakan StudentVM memerlukan penggunaan StudentVM halaman Buat daripada Student:

@page
@model CreateVMModel

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

<h1>Create</h1>

<h4>Student</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="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </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");}
}

Memperbarui halaman Edit

Di Pages/Students/Edit.cshtml.cs, ganti OnGetAsync metode dan OnPostAsync dengan kode berikut.

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

    Student = await _context.Students.FindAsync(id);

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

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

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

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Perubahan kode mirip dengan halaman Buat dengan beberapa pengecualian:

  • FirstOrDefaultAsync telah diganti dengan FindAsync. Ketika Anda tidak perlu menyertakan data terkait, FindAsync lebih efisien.
  • OnPostAsync memiliki id parameter .
  • Siswa saat ini diambil dari database, daripada membuat siswa kosong.

Jalankan aplikasi, dan uji dengan membuat dan mengedit siswa.

Status Entitas

Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database. Informasi pelacakan ini menentukan apa yang terjadi ketika SaveChangesAsync dipanggil. Misalnya, ketika entitas baru diteruskan ke AddAsync metode , status entitas tersebut diatur ke Added. Ketika SaveChangesAsync dipanggil, konteks database mengeluarkan perintah SQL INSERT .

Entitas mungkin berada di salah satu status berikut:

  • Added: Entitas belum ada di database. Metode mengeluarkan SaveChangesINSERT pernyataan.

  • Unchanged: Tidak ada perubahan yang perlu disimpan dengan entitas ini. Entitas memiliki status ini saat dibaca dari database.

  • Modified: Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode mengeluarkan SaveChangesUPDATE pernyataan.

  • Deleted: Entitas telah ditandai untuk penghapusan. Metode mengeluarkan SaveChangesDELETE pernyataan.

  • Detached: Entitas tidak dilacak oleh konteks database.

Di aplikasi desktop, perubahan status biasanya diatur secara otomatis. Entitas dibaca, perubahan dilakukan, dan status entitas secara otomatis diubah menjadi Modified. SaveChanges Panggilan menghasilkan pernyataan SQL UPDATE yang hanya memperbarui properti yang diubah.

Di aplikasi web, DbContext yang membaca entitas dan menampilkan data dibuang setelah halaman dirender. Ketika metode halaman OnPostAsync dipanggil, permintaan web baru dibuat dan dengan instans baru dari DbContext. Pembacaan ulang entitas dalam konteks baru mensimulasikan pemrosesan desktop.

Memperbarui halaman Hapus

Di bagian ini, pesan kesalahan kustom diimplementasikan saat panggilan gagal SaveChanges .

Ganti kode di Pages/Students/Delete.cshtml.cs dengan kode berikut:

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

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<DeleteModel> _logger;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context,
                           ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

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

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

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

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
            }

            return Page();
        }

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

            var student = await _context.Students.FindAsync(id);

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

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, ErrorMessage);

                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Kode sebelumnya:

  • Menambahkan Pengelogan.
  • Menambahkan parameter saveChangesError opsional ke OnGetAsync tanda tangan metode. saveChangesError menunjukkan apakah metode dipanggil setelah kegagalan untuk menghapus objek siswa.

Operasi penghapusan mungkin gagal karena masalah jaringan sementara. Kesalahan jaringan sementara lebih mungkin terjadi ketika database berada di cloud. Parameternya saveChangesError adalah false ketika halaman OnGetAsync Hapus dipanggil dari UI. Ketika OnGetAsync dipanggil oleh OnPostAsync karena operasi penghapusan gagal, saveChangesError parameternya adalah true.

Metode mengambil OnPostAsync entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted. Ketika SaveChanges dipanggil, perintah SQL DELETE dihasilkan. Jika Remove gagal:

  • Pengecualian database tertangkap.
  • Metode Hapus halaman OnGetAsync dipanggil dengan saveChangesError=true.

Tambahkan pesan kesalahan ke Pages/Students/Delete.cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

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

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Jalankan aplikasi dan hapus siswa untuk menguji halaman Hapus.

Langkah berikutnya

Dalam tutorial ini, kode CRUD (buat, baca, perbarui, hapus) perancah ditinjau dan disesuaikan.

Tidak ada repositori

Beberapa pengembang menggunakan lapisan layanan atau pola repositori untuk membuat lapisan abstraksi antara UI (Razor Pages) dan lapisan akses data. Tutorial ini tidak melakukan itu. Untuk meminimalkan kompleksitas dan menjaga tutorial tetap fokus pada EF Core, EF Core kode ditambahkan langsung ke kelas model halaman.

Memperbarui halaman Detail

Kode perancah untuk halaman Siswa tidak menyertakan data pendaftaran. Di bagian ini, pendaftaran ditambahkan ke halaman Detail.

Membaca pendaftaran

Untuk menampilkan data pendaftaran siswa di halaman, data pendaftaran perlu dibaca. Kode perancah dalam Pages/Students/Details.cshtml.cs hanya membaca data Siswa, tanpa data Pendaftaran:

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

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

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

OnGetAsync Ganti metode dengan kode berikut untuk membaca data pendaftaran untuk siswa yang dipilih. Perubahan disorot.

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

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

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

Metode Include dan ThenInclude menyebabkan konteks memuat Student.Enrollments properti navigasi, dan dalam setiap pendaftaran Enrollment.Course properti navigasi. Metode ini diperiksa secara rinci dalam tutorial Membaca data terkait.

Metode ini AsNoTracking meningkatkan performa dalam skenario di mana entitas yang dikembalikan tidak diperbarui dalam konteks saat ini. AsNoTracking dibahas nanti dalam tutorial ini.

Tampilkan pendaftaran

Ganti kode dengan Pages/Students/Details.cshtml kode berikut untuk menampilkan daftar pendaftaran. Perubahan disorot.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

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

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Kode sebelumnya mengulangi entitas di Enrollments properti navigasi. Untuk setiap pendaftaran, ini menampilkan judul kursus dan nilai. Judul kursus diambil dari entitas Kursus yang disimpan di Course properti navigasi entitas Pendaftaran.

Jalankan aplikasi, pilih tab Siswa , dan klik tautan Detail untuk siswa. Daftar kursus dan nilai untuk siswa yang dipilih ditampilkan.

Cara membaca satu entitas

Kode yang dihasilkan menggunakan FirstOrDefaultAsync untuk membaca satu entitas. Metode ini mengembalikan null jika tidak ada yang ditemukan; jika tidak, ini mengembalikan baris pertama yang ditemukan yang memenuhi kriteria filter kueri. FirstOrDefaultAsync umumnya adalah pilihan yang lebih baik daripada alternatif berikut:

  • SingleOrDefaultAsync - Melemparkan pengecualian jika ada lebih dari satu entitas yang memenuhi filter kueri. Untuk menentukan apakah lebih dari satu baris dapat dikembalikan oleh kueri, SingleOrDefaultAsync coba ambil beberapa baris. Pekerjaan tambahan ini tidak perlu jika kueri hanya dapat mengembalikan satu entitas, seperti ketika mencari pada kunci unik.
  • FindAsync - Menemukan entitas dengan kunci primer (PK). Jika entitas dengan PK sedang dilacak oleh konteks, entitas dikembalikan tanpa permintaan ke database. Metode ini dioptimalkan untuk mencari satu entitas, tetapi Anda tidak dapat memanggil Include dengan FindAsync. Jadi jika data terkait diperlukan, FirstOrDefaultAsync adalah pilihan yang lebih baik.

Merutekan data vs. string kueri

URL untuk halaman Detail adalah https://localhost:<port>/Students/Details?id=1. Nilai kunci utama entitas berada dalam string kueri. Beberapa pengembang lebih suka meneruskan nilai kunci dalam data rute: https://localhost:<port>/Students/Details/1. Untuk informasi selengkapnya, lihat Memperbarui kode yang dihasilkan.

Memperbarui halaman Buat

Kode perancah OnPostAsync untuk halaman Buat rentan terhadap overposting. OnPostAsync Ganti metode dengan Pages/Students/Create.cshtml.cs kode berikut.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Kode sebelumnya membuat objek Siswa lalu menggunakan bidang formulir yang diposting untuk memperbarui properti objek Siswa. Metode TryUpdateModelAsync :

  • Menggunakan nilai formulir yang diposting dari PageContext properti di PageModel.
  • Memperbarui hanya properti yang tercantum (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Mencari bidang formulir dengan awalan "siswa". Misalnya, Student.FirstMidName. Ini tidak peka huruf besar/kecil.
  • Menggunakan sistem pengikatan model untuk mengonversi nilai formulir dari string ke jenis dalam Student model. Misalnya, EnrollmentDate harus dikonversi ke DateTime.

Jalankan aplikasi, dan buat entitas siswa untuk menguji halaman Buat.

Overposting

Menggunakan TryUpdateModel untuk memperbarui bidang dengan nilai yang diposting adalah praktik terbaik keamanan karena mencegah overposting. Misalnya, entitas Siswa menyertakan Secret properti yang tidak boleh diperbarui atau ditambahkan halaman web ini:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Bahkan jika aplikasi tidak memiliki Secret bidang di Halaman buat atau perbarui Razor , peretas dapat mengatur nilai dengan Secret melakukan overposting. Peretas dapat menggunakan alat seperti Fiddler, atau menulis beberapa JavaScript, untuk memposting Secret nilai formulir. Kode asli tidak membatasi bidang yang digunakan pengikat model saat membuat instans Siswa.

Nilai apa pun yang ditentukan peretas untuk Secret bidang formulir diperbarui dalam database. Gambar berikut menunjukkan alat Fiddler yang menambahkan Secret bidang (dengan nilai "OverPost") ke nilai formulir yang diposting.

Fiddler adding Secret field

Nilai "OverPost" berhasil ditambahkan ke Secret properti baris yang disisipkan. Itu terjadi meskipun perancang aplikasi tidak pernah menginginkan Secret properti diatur dengan halaman Buat.

Lihat model

Model tampilan menyediakan cara alternatif untuk mencegah overposting.

Model aplikasi sering disebut model domain. Model domain biasanya berisi semua properti yang diperlukan oleh entitas yang sesuai dalam database. Model tampilan hanya berisi properti yang diperlukan untuk UI yang digunakannya (misalnya, halaman Buat).

Selain model tampilan, beberapa aplikasi menggunakan model pengikatan atau model input untuk meneruskan data antara Razor kelas model halaman Halaman dan browser.

Pertimbangkan model tampilan berikut Student :

using System;

namespace ContosoUniversity.Models
{
    public class StudentVM
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

Kode berikut menggunakan StudentVM model tampilan untuk membuat siswa baru:

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

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

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Metode SetValues mengatur nilai objek ini dengan membaca nilai dari objek lain PropertyValues . SetValues menggunakan pencocokan nama properti. Jenis model tampilan tidak perlu terkait dengan jenis model, hanya perlu memiliki properti yang cocok.

Menggunakan StudentVM mengharuskan Create.cshtml diperbarui untuk digunakan StudentVM daripada Student.

Memperbarui halaman Edit

Di Pages/Students/Edit.cshtml.cs, ganti OnGetAsync metode dan OnPostAsync dengan kode berikut.

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

    Student = await _context.Students.FindAsync(id);

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

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

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

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Perubahan kode mirip dengan halaman Buat dengan beberapa pengecualian:

  • FirstOrDefaultAsync telah diganti dengan FindAsync. Ketika data terkait yang disertakan tidak diperlukan, FindAsync lebih efisien.
  • OnPostAsync memiliki id parameter .
  • Siswa saat ini diambil dari database, daripada membuat siswa kosong.

Jalankan aplikasi, dan uji dengan membuat dan mengedit siswa.

Status Entitas

Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database. Informasi pelacakan ini menentukan apa yang terjadi ketika SaveChangesAsync dipanggil. Misalnya, ketika entitas baru diteruskan ke AddAsync metode , status entitas tersebut diatur ke Added. Ketika SaveChangesAsync dipanggil, konteks database mengeluarkan perintah SQL INSERT.

Entitas mungkin berada di salah satu status berikut:

  • Added: Entitas belum ada di database. Metode mengeluarkan SaveChanges pernyataan INSERT.

  • Unchanged: Tidak ada perubahan yang perlu disimpan dengan entitas ini. Entitas memiliki status ini saat dibaca dari database.

  • Modified: Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode ini SaveChanges mengeluarkan pernyataan UPDATE.

  • Deleted: Entitas telah ditandai untuk penghapusan. Metode mengeluarkan SaveChanges pernyataan DELETE.

  • Detached: Entitas tidak dilacak oleh konteks database.

Di aplikasi desktop, perubahan status biasanya diatur secara otomatis. Entitas dibaca, perubahan dilakukan, dan status entitas secara otomatis diubah menjadi Modified. SaveChanges Panggilan menghasilkan pernyataan PEMBARUAN SQL yang hanya memperbarui properti yang diubah.

Di aplikasi web, DbContext yang membaca entitas dan menampilkan data dibuang setelah halaman dirender. Ketika metode halaman OnPostAsync dipanggil, permintaan web baru dibuat dan dengan instans baru dari DbContext. Pembacaan ulang entitas dalam konteks baru mensimulasikan pemrosesan desktop.

Memperbarui halaman Hapus

Di bagian ini, Anda menerapkan pesan kesalahan kustom saat panggilan gagal SaveChanges .

Ganti kode dengan Pages/Students/Delete.cshtml.cs kode berikut. Perubahan disorot (selain pembersihan using pernyataan).

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

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

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

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

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

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

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

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = "Delete failed. Try again";
            }

            return Page();
        }

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

            var student = await _context.Students.FindAsync(id);

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

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Kode sebelumnya menambahkan parameter saveChangesError opsional ke OnGetAsync tanda tangan metode. saveChangesError menunjukkan apakah metode dipanggil setelah kegagalan untuk menghapus objek siswa. Operasi penghapusan mungkin gagal karena masalah jaringan sementara. Kesalahan jaringan sementara lebih mungkin terjadi ketika database berada di cloud. Parameter saveChangesError salah ketika halaman OnGetAsync Hapus dipanggil dari UI. Ketika OnGetAsync dipanggil oleh OnPostAsync (karena operasi penghapusan gagal), saveChangesError parameter benar.

Metode mengambil OnPostAsync entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted. Ketika SaveChanges dipanggil, perintah SQL DELETE dihasilkan. Jika Remove gagal:

  • Pengecualian database tertangkap.
  • Metode Hapus halaman OnGetAsync dipanggil dengan saveChangesError=true.

Tambahkan pesan kesalahan ke Halaman Hapus Razor (Pages/Students/Delete.cshtml):

@page
@model ContosoUniversity.Pages.Students.DeleteModel

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

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Jalankan aplikasi dan hapus siswa untuk menguji halaman Hapus.

Langkah berikutnya