ASP.NET Core: Triển khai chức năng CRUD - ASP.NET MVC với EF Core
Trong bài viết này
- Điều kiện tiên quyết
- Tùy chỉnh trang Details
- Cập nhật trang Create
- Cập nhật trang Edit
- Cập nhật trang Delete
- Đóng kết nối cơ sở dữ liệu
- Xử lý giao dịch
- Truy vấn không theo dõi
- Lấy mã
- Bước tiếp theo
Trong hướng dẫn trước, bạn đã tạo một ứng dụng MVC lưu trữ và hiển thị dữ liệu bằng Entity Framework và SQL Server LocalDB. Trong hướng dẫn này, bạn sẽ xem xét và tùy chỉnh mã CRUD (Create, Read, Update, Delete) mà scaffold MVC tự động tạo cho bạn trong controller và view.
Ghi chú
Thực tế phổ biến là triển khai mẫu kho lưu trữ để tạo lớp trừu tượng giữa controller của bạn và lớp truy cập dữ liệu. Để giữ cho những hướng dẫn này đơn giản và tập trung vào việc dạy cách sử dụng Entity Framework, chúng không sử dụng kho lưu trữ. Để biết thông tin về các kho lưu trữ với EF, hãy xem hướng dẫn cuối cùng trong loạt bài này.
Trong hướng dẫn này, bạn:
- Tùy chỉnh trang Details
- Cập nhật trang Create
- Cập nhật trang Update
- Cập nhật trang Delete
- Đóng kết nối cơ sở dữ liệu
Điều kiện tiên quyết
Tùy chỉnh trang Details
Mã được scaffold cho trang Students Index đã loại bỏ property Enrollments
vì property đó chứa một collection. Trong trang Details, bạn sẽ hiển thị nội dung của collection trong bảng HTML.
Trong Controllers/StudentsController.cs
, action method cho view Details sử dụng phương thức FirstOrDefaultAsync
để truy xuất một thực thể Student
. Thêm mã để gọi Include
. Các phương thức ThenInclude
và AsNoTracking
, như được hiển thị trong mã được tô sáng sau đây.
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}
return View(student);
}
Các phương thức Include
và ThenInclude
khiến ngữ cảnh tải property điều hướng Student.Enrollments
và trong mỗi lần đăng ký property điều hướng Enrollment.Course
. Bạn sẽ tìm hiểu thêm về các phương thức này trong phần hướng dẫn đọc dữ liệu liên quan.
Phương thức AsNoTracking
cải thiện hiệu suất trong các trường hợp trong đó các thực thể được trả về sẽ không được cập nhật trong thời gian tồn tại của ngữ cảnh hiện tại. Bạn sẽ tìm hiểu thêm AsNoTracking
ở phần cuối của hướng dẫn này.
Dữ liệu định tuyến
Giá trị khóa được truyền cho phương thức Details
đến từ dữ liệu định tuyến. Dữ liệu định tuyến là dữ liệu mà mô hình liên kết tìm thấy trong một đoạn URL. Ví dụ: định tuyến mặc định chỉ định các phân đoạn controller, action và id:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Trong URL sau, định tuyến mặc định ánh xạ Instructor làm controller, Index làm hành động và 1 làm id; đây là những giá trị dữ liệu định tuyến.
http://localhost:1230/Instructor/Index/1?courseID=2021
Phần cuối cùng của URL ("?courseID=2021") là giá trị chuỗi truy vấn. Trình liên kết model cũng sẽ truyền giá trị ID cho tham số id
của phương thức Index
nếu bạn truyền nó dưới dạng giá trị chuỗi truy vấn:
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
Trong trang Index, URL siêu liên kết được tạo bằng các câu lệnh trợ giúp thẻ trong view Razor. Trong mã Razor sau đây, tham số id
khớp với định tuyến mặc định, do đó id
được thêm vào dữ liệu định tuyến.
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>
Điều này tạo ra HTML sau khi item.ID
là 6:
<a href="/Students/Edit/6">Edit</a>
Trong mã Razor sau đây, studentID
không khớp với một tham số trong định tuyến mặc định nên nó được thêm dưới dạng chuỗi truy vấn.
<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>
Điều này tạo ra HTML sau khi item.ID
là 6:
<a href="/Students/Edit?studentID=6">Edit</a>
Để biết thêm thông tin về trình trợ giúp thẻ, hãy xem Tag Helper trong ASP.NET Core.
Thêm đăng ký vào view Details
Mở Views/Students/Details.cshtml
. Mỗi trường được hiển thị bằng cách sử dụng các helper DisplayNameFor
và DisplayFor
, như trong ví dụ sau:
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.LastName)
</dd>
Sau trường cuối cùng và ngay trước thẻ đóng </dl>
, hãy thêm đoạn mã sau để hiển thị danh sách đăng ký:
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
Nếu thụt lề mã sai sau khi bạn dán mã, hãy nhấn CTRL-K-D để sửa mã.
Mã này lặp qua các thực thể trong property điều hướng Enrollments
. Đối với mỗi lần đăng ký, nó sẽ hiển thị tên khóa học và điểm. Tiêu đề khóa học được truy xuất từ thực thể Course được lưu trữ trong property điều hướng Course
của thực thể Enrollments.
Chạy ứng dụng, chọn tab Students và nhấp vào liên kết Details cho mỗi sinh viên. Bạn sẽ xem được danh sách các khóa học và điểm số của sinh viên đã chọn:
Cập nhật trang Create
Trong StudentsController.cs
, sửa đổi phương thức HttpPost Create
bằng cách thêm khối try-catch và xóa ID khỏi attribute Bind
.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
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 View(student);
}
Mã này thêm thực thể Student được tạo bởi trình liên kết model ASP.NET Core MVC vào tập thực thể Students rồi lưu các thay đổi vào cơ sở dữ liệu. (Trình liên kết model đề cập đến chức năng ASP.NET Core MVC giúp bạn làm việc với dữ liệu được gửi bởi biểu mẫu dễ dàng hơn; liên kết model chuyển đổi các giá trị biểu mẫu đã đăng thành loại CLR và chuyển chúng sang phương thức hành động trong các tham số. Trong trường hợp này, trình liên kết model sẽ khởi tạo một thực thể Student cho bạn bằng cách sử dụng các giá trị property từ collection Form.)
Bạn đã xóa ID
khỏi attribute Bind
vì ID là giá trị khóa chính mà SQL Server sẽ tự động đặt khi hàng được chèn. Đầu vào từ người dùng không đặt giá trị ID.
Ngoài attribute Bind
, khối try-catch là thay đổi duy nhất bạn thực hiện đối với mã được scaffold. Nếu một ngoại lệ bắt nguồn từ DbUpdateException
bị phát hiện trong khi các thay đổi đang được lưu thì một thông báo lỗi chung sẽ được hiển thị. Các ngoại lệ DbUpdateException
đôi khi xảy ra do điều gì đó bên ngoài ứng dụng chứ không phải do lỗi lập trình, vì vậy người dùng nên thử lại. Mặc dù không được triển khai trong mẫu này nhưng ứng dụng ở mức production sẽ ghi lại ngoại lệ. Để biết thêm thông tin, hãy xem phần Nhật ký để biết thông tin chi tiết trong Giám sát và đo từ xa (Xây dựng ứng dụng đám mây trong thế giới thực với Azure).
Attribute ValidateAntiForgeryToken
giúp ngăn chặn các cuộc tấn công giả mạo yêu cầu chéo trang (CSRF). Mã thông báo được FormTagHelper tự động đưa vào view và được đưa vào khi người dùng gửi form. Mã thông báo được xác thực bởi attribute ValidateAntiForgeryToken
. Để biết thêm thông tin, hãy xem Ngăn chặn các cuộc tấn công giả mạo yêu cầu chéo trang web (XSRF/CSRF) trong ASP.NET Core.
Lưu ý bảo mật về việc đăng quá nhiều
Attribute Bind
mà mã được scaffold bao gồm trong phương thức Create
là một cách để bảo vệ khỏi việc đăng quá nhiều trong các tình huống tạo. Ví dụ: giả sử thực thể Student bao gồm một property Secret
mà bạn không muốn trang web này thiết lập.
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; }
}
Ngay cả khi bạn không có trường Secret
trên trang web, tin tặc vẫn có thể sử dụng một công cụ như Fiddler hoặc viết một số mã JavaScript để đăng giá trị form Secret
. Nếu không có attribute Bind
giới hạn các trường mà trình liên kết model sử dụng khi tạo một phiên bản Student, thì trình liên kết model sẽ chọn giá trị form Secret
đó và sử dụng nó để tạo phiên bản thực thể Student. Sau đó, bất kỳ giá trị nào mà hacker chỉ định cho trường form Secret
sẽ được cập nhật trong cơ sở dữ liệu của bạn. Hình ảnh sau đây hiển thị công cụ Fiddler đang thêm trường Secret
(có giá trị "OverPost") vào các giá trị form đã đăng.
Giá trị "OverPost" sau đó sẽ được thêm thành công vào property Secret
của hàng được chèn, mặc dù bạn chưa bao giờ dự định rằng trang web có thể thiết lập property đó.
Bạn có thể ngăn việc đăng quá mức trong các tình huống chỉnh sửa bằng cách đọc thực thể từ cơ sở dữ liệu trước rồi gọi TryUpdateModel
, truyền vào danh sách property được phép tường minh. Đó là phương pháp được sử dụng trong các hướng dẫn này.
Một cách khác để ngăn chặn việc đăng quá mức được nhiều nhà phát triển ưa thích là sử dụng các view model thay vì các lớp thực thể có liên kết model. Chỉ bao gồm các property bạn muốn cập nhật trong view model. Sau khi liên kết model MVC đã hoàn tất, hãy sao chép các property của view model vào thể hiện thực thể, tùy ý sử dụng công cụ như AutoMapper. Sử dụng _context.Entry
trên phiên bản thực thể để đặt trạng thái của nó thành Unchanged
, sau đó đặt Property("PropertyName").IsModified
thành true trên mỗi property thực thể được bao gồm trong view model. Phương pháp này hoạt động trong cả chỉnh sửa và tạo kịch bản.
Kiểm tra trang Create
Mã Views/Students/Create.cshtml
sử dụng các tag helper label
, input
, và span
(đối với các tin nhắn xác thực) cho từng trường.
Chạy ứng dụng, chọn tab Students và nhấp vào Create New.
Nhập tên và ngày tháng. Hãy thử nhập ngày không hợp lệ nếu trình duyệt của bạn cho phép bạn làm điều đó. (Một số trình duyệt buộc bạn phải sử dụng công cụ chọn ngày.) Sau đó nhấp vào Create để xem thông báo lỗi.
Đây là xác thực phía máy chủ mà bạn nhận được theo mặc định; trong phần hướng dẫn sau, bạn sẽ biết cách thêm các attribute sẽ tạo mã để xác thực phía máy khách. Mã được đánh dấu sau đây hiển thị quá trình kiểm tra xác thực model trong phương thức Create
.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
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 View(student);
}
Thay đổi ngày thành giá trị hợp lệ và nhấp vào Create để xem học sinh mới xuất hiện trong trang Index.
Cập nhật trang Edit
Trong StudentController.cs
, phương thức HttpGet Edit
(phương thức không có attribute HttpPost
) sử dụng phương thức FirstOrDefaultAsync
để truy xuất thực thể Student đã chọn, như bạn đã thấy trong phương thức Details
. Bạn không cần phải thay đổi phương thức này.
Mã chỉnh sửa HttpPost được đề xuất: Đọc và cập nhật
Thay thế phương thức hành động HttpPost Edit bằng mã sau.
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
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 View(studentToUpdate);
}
Những thay đổi này triển khai phương pháp bảo mật tốt nhất để ngăn chặn việc đăng quá mức. Trình scaffold đã tạo một attribute Bind
và thêm thực thể được tạo bởi liên kết model vào tập thực thể có cờ Modified
. Mã đó không được khuyến nghị cho nhiều trường hợp vì attribute Bind
sẽ xóa mọi dữ liệu có sẵn trong các trường không được liệt kê trong tham số Include
.
Mã mới đọc thực thể hiện có và gọi TryUpdateModel
để cập nhật các trường trong thực thể được truy xuất dựa trên dữ liệu nhập của người dùng trong dữ liệu form đã đăng. Tính năng theo dõi thay đổi tự động của Entity Framework đặt cờ Modified
trên các trường được thay đổi khi nhập form. Khi phương thức SaveChanges
được gọi, Entity Framework sẽ tạo các câu lệnh SQL để cập nhật hàng cơ sở dữ liệu. Xung đột đồng thời bị bỏ qua và chỉ các cột trong bảng được người dùng cập nhật mới được cập nhật trong cơ sở dữ liệu. (Hướng dẫn sau sẽ trình bày cách xử lý xung đột đồng thời.)
Cách tốt nhất để ngăn chặn việc đăng quá mức là các trường mà bạn muốn trang Edit có thể cập nhật được khai báo trong các tham số TryUpdateModel
. (Chuỗi trống trước danh sách các trường trong danh sách tham số là dành cho tiền tố để sử dụng với tên trường biểu mẫu.) Hiện tại không có trường bổ sung nào mà bạn đang bảo vệ nhưng liệt kê các trường mà bạn muốn trình liên kết model liên kết đảm bảo rằng nếu bạn thêm các trường vào mô hình dữ liệu trong tương lai, chúng sẽ tự động được bảo vệ cho đến khi bạn thêm chúng vào đây một cách rõ ràng.
Do những thay đổi này, chữ ký (signature) phương thức của phương thức HttpPost Edit
giống với phương thức HttpGet Edit
; vì vậy bạn đã đổi tên phương thức EditPost
.
Mã chỉnh sửa HttpPost thay thế: Tạo và đính kèm
Mã chỉnh sửa HttpPost được đề xuất đảm bảo rằng chỉ những cột đã thay đổi mới được cập nhật và lưu giữ dữ liệu trong các property mà bạn không muốn đưa vào liên kết model. Tuy nhiên, cách tiếp cận đọc trước yêu cầu đọc cơ sở dữ liệu bổ sung và có thể tạo ra mã phức tạp hơn để xử lý xung đột đồng thời. Một cách khác là đính kèm một thực thể được tạo bởi trình liên kết model vào ngữ cảnh EF và đánh dấu nó là đã sửa đổi. (Không cập nhật dự án của bạn bằng mã này, nó chỉ được hiển thị để minh họa một cách tiếp cận tùy chọn.)
public async Task<IActionResult> Edit(int id, [Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
if (id != student.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
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 View(student);
}
Bạn có thể sử dụng giải pháp này khi giao diện người dùng trang web bao gồm tất cả các trường trong thực thể và có thể cập nhật bất kỳ trường nào trong số đó.
Mã được scaffold sử dụng giải pháp tạo và đính kèm nhưng chỉ bắt các ngoại lệ DbUpdateConcurrencyException
và trả về mã lỗi 404. Ví dụ hiển thị sẽ phát hiện bất kỳ ngoại lệ cập nhật cơ sở dữ liệu nào và hiển thị thông báo lỗi.
Các trạng thái thực thể
Ngữ cảnh cơ sở dữ liệu theo dõi xem các thực thể trong bộ nhớ có đồng bộ với các hàng tương ứng của chúng trong cơ sở dữ liệu hay không và thông tin này xác định điều gì xảy ra khi bạn gọi phương thức SaveChanges
. Ví dụ: khi bạn truyền một thực thể mới vào phương thức Add
, trạng thái của thực thể đó được đặt thành Added
. Sau đó, khi bạn gọi phương thức SaveChanges
, ngử cảnh cơ sở dữ liệu sẽ đưa ra lệnh SQL INSERT.
Một thực thể có thể ở một trong các trạng thái sau:
-
Added
. Thực thể này chưa tồn tại trong cơ sở dữ liệu. Phương thứcSaveChanges
đưa ra câu lệnh INSERT. -
Unchanged
. Không cần phải làm gì với thực thể này bằng phương thứcSaveChanges
. Khi bạn đọc một thực thể từ cơ sở dữ liệu, thực thể đó sẽ bắt đầu với trạng thái này. -
Modified
. Một số hoặc tất cả các giá trị property của thực thể đã được sửa đổi. Phương thứcSaveChanges
đưa ra một câu lệnh UPDATE. -
Deleted
. Thực thể đã được đánh dấu để xóa. Phương thứcSaveChanges
đưa ra câu lệnh DELETE. -
Detached
. Thực thể không được theo dõi bởi ngữ cảnh cơ sở dữ liệu.
Trong ứng dụng dành cho máy tính để bàn, các thay đổi trạng thái thường được đặt tự động. Bạn đọc một thực thể và thực hiện thay đổi đối với một số giá trị property của nó. Điều này khiến trạng thái thực thể của nó tự động được thay đổi thành Modified
. Sau đó, khi bạn gọi SaveChanges
, Entity Framework sẽ tạo ra một câu lệnh SQL UPDATE chỉ cập nhật các property thực tế mà bạn đã thay đổi.
Trong một ứng dụng web, ứng dụng DbContext
ban đầu đọc một thực thể và hiển thị dữ liệu cần chỉnh sửa của nó sẽ được xử lý sau khi một trang được hiển thị. Khi phương thức hành động HttpPost Edit
được gọi, một yêu cầu web mới sẽ được thực hiện và bạn có một phiên bản mới của DbContext
. Nếu bạn đọc lại thực thể trong ngữ cảnh mới đó, bạn sẽ mô phỏng quá trình xử lý trên máy tính để bàn.
Nhưng nếu bạn không muốn thực hiện thao tác đọc thêm, bạn phải sử dụng đối tượng thực thể được tạo bởi trình liên kết model. Cách đơn giản nhất để thực hiện việc này là đặt trạng thái thực thể thành Modified như được thực hiện trong mã HttpPost Edit thay thế được hiển thị trước đó. Sau đó, khi bạn gọi SaveChanges
, Entity Framework sẽ cập nhật tất cả các cột của hàng cơ sở dữ liệu, vì ngữ cảnh không có cách nào để biết bạn đã thay đổi property nào.
Nếu bạn muốn tránh cách tiếp cận đọc trước nhưng cũng muốn câu lệnh SQL UPDATE chỉ cập nhật các trường mà người dùng thực sự đã thay đổi thì mã sẽ phức tạp hơn. Bạn phải lưu các giá trị ban đầu theo một cách nào đó (chẳng hạn như bằng cách sử dụng các trường ẩn) để chúng có sẵn khi phương thức HttpPost Edit
được gọi. Sau đó, bạn có thể tạo một thực thể Student bằng cách sử dụng các giá trị ban đầu, gọi phương thức Attach
với phiên bản ban đầu của thực thể đó, cập nhật các giá trị của thực thể đó thành các giá trị mới, sau đó gọi SaveChanges
.
Kiểm tra trang Edit
Chạy ứng dụng, chọn tab Students, sau đó bấm vào siêu liên kết Edit.
Thay đổi một số dữ liệu và nhấn Save. Trang Index mở ra và bạn thấy dữ liệu đã thay đổi.
Cập nhật trang Delete
Trong StudentController.cs
, mã mẫu cho phương thức HttpGet Delete
sử dụng phương thức FirstOrDefaultAsync
để truy xuất thực thể Student đã chọn, như bạn đã thấy trong các phương thức Details và Edit. Tuy nhiên, để triển khai thông báo lỗi tùy chỉnh khi lệnh gọi SaveChanges
không thành công, bạn sẽ thêm một số chức năng vào phương thức này và view tương ứng của nó.
Như bạn đã thấy đối với các thao tác update và create, các thao tác delete yêu cầu hai phương thức hành động. Phương thức được gọi để phản hồi yêu cầu GET sẽ hiển thị view cho phép người dùng có cơ hội phê duyệt hoặc hủy thao tác xóa. Nếu người dùng chấp thuận, yêu cầu POST sẽ được tạo. Khi điều đó xảy ra, phương thức HttpPost Delete
được gọi và sau đó phương thức đó thực sự thực hiện thao tác xóa.
Bạn sẽ thêm khối try-catch vào phương thức HttpPost Delete
để xử lý mọi lỗi có thể xảy ra khi cơ sở dữ liệu được cập nhật. Nếu xảy ra lỗi, phương thức HttpPost Delete sẽ gọi phương thức HttpGet Delete, truyền cho nó một tham số cho biết đã xảy ra lỗi. Phương thức HttpGet Delete sau đó sẽ hiển thị lại trang xác nhận cùng với thông báo lỗi, cho người dùng cơ hội hủy hoặc thử lại.
Thay thế phương thức hành động HttpGet Delete
bằng đoạn mã sau để quản lý việc báo cáo lỗi.
public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"Delete failed. Try again, and if the problem persists " +
"see your system administrator.";
}
return View(student);
}
Mã này chấp nhận một tham số tùy chọn cho biết liệu phương thức có được gọi sau khi không lưu được các thay đổi hay không. Tham số này là sai khi phương thức HttpGet Delete
được gọi mà không gặp lỗi trước đó. Khi nó được gọi bằng phương thức HttpPost Delete
để phản hồi lỗi cập nhật cơ sở dữ liệu, tham số này là đúng và thông báo lỗi sẽ được truyền đến view.
Cách tiếp cận đọc đầu tiên để xóa HttpPost
Thay thế phương thức hành động HttpPost Delete
(có tên DeleteConfirmed
) bằng mã sau đây để thực hiện thao tác xóa thực tế và phát hiện mọi lỗi cập nhật cơ sở dữ liệu.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students.FindAsync(id);
if (student == null)
{
return RedirectToAction(nameof(Index));
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}
Mã này truy xuất thực thể đã chọn, sau đó gọi phương thức Remove
để đặt trạng thái của thực thể thành Deleted
. Khi SaveChanges
được gọi, lệnh SQL DELETE sẽ được tạo.
Cách tiếp cận tạo và đính kèm để xóa HttpPost
Nếu ưu tiên cải thiện hiệu suất trong ứng dụng có khối lượng lớn thì bạn có thể tránh truy vấn SQL không cần thiết bằng cách khởi tạo thực thể Student chỉ bằng giá trị khóa chính rồi đặt trạng thái thực thể thành Deleted
. Đó là tất cả những gì Entity Framework cần để xóa thực thể. (Đừng đặt mã này vào dự án của bạn; nó ở đây chỉ để minh họa một giải pháp thay thế.)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
}
}
Nếu thực thể có dữ liệu liên quan cũng cần bị xóa, hãy đảm bảo rằng tính năng xóa theo tầng được đặt cấu hình trong cơ sở dữ liệu. Với phương pháp xóa thực thể này, EF có thể không nhận ra có những thực thể liên quan cần xóa.
Cập nhật view Delete
Trong Views/Student/Delete.cshtml
, thêm thông báo lỗi giữa tiêu đề h2 và tiêu đề h3, như trong ví dụ sau:
<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>
Chạy ứng dụng, chọn tab Students và nhấp vào siêu liên kết Delete:
Nhấp vào Delete. Trang Index được hiển thị mà không có sinh viên nào bị xóa. (Bạn sẽ thấy một ví dụ về mã xử lý lỗi đang hoạt động trong hướng dẫn tương tranh.)
Đóng kết nối cơ sở dữ liệu
Để giải phóng các tài nguyên mà kết nối cơ sở dữ liệu nắm giữ, phiên bản ngữ cảnh phải được xử lý càng sớm càng tốt khi bạn hoàn tất việc đó. Tính năng chèn phụ thuộc tích hợp sẵn của ASP.NET Core sẽ đảm nhiệm công việc đó cho bạn.
Trong Startup.cs
, bạn gọi phương thức mở rộng AddDbContext để cung cấp lớp DbContext
trong bộ chứa ASP.NET Core DI. Phương thức này sẽ đặt thời gian sử dụng dịch vụ Scoped
theo mặc định. Scoped
có nghĩa là thời gian tồn tại của đối tượng ngữ cảnh trùng với thời gian tồn tại của yêu cầu web và phương thức Dispose
sẽ được gọi tự động khi kết thúc yêu cầu web.
Xử lý giao dịch
Theo mặc định, Entity Framework ngầm thực hiện các giao dịch. Trong trường hợp bạn thực hiện thay đổi đối với nhiều hàng hoặc bảng rồi gọi SaveChanges
, Entity Framework sẽ tự động đảm bảo rằng tất cả các thay đổi của bạn đều thành công hoặc tất cả đều thất bại. Nếu một số thay đổi được thực hiện trước và sau đó xảy ra lỗi thì những thay đổi đó sẽ tự động được khôi phục. Đối với các trường hợp mà bạn cần kiểm soát nhiều hơn -- ví dụ: nếu bạn muốn bao gồm các hoạt động được thực hiện bên ngoài Entity Framework trong một giao dịch -- hãy xem Giao dịch.
Truy vấn không theo dõi
Khi một ngữ cảnh cơ sở dữ liệu truy xuất các hàng của bảng và tạo các đối tượng thực thể đại diện cho chúng, theo mặc định, nó sẽ theo dõi xem các thực thể trong bộ nhớ có đồng bộ với những gì có trong cơ sở dữ liệu hay không. Dữ liệu trong bộ nhớ hoạt động như bộ đệm và được sử dụng khi bạn cập nhật một thực thể. Bộ nhớ đệm này thường không cần thiết trong ứng dụng web vì các phiên bản ngữ cảnh thường tồn tại trong thời gian ngắn (một phiên bản mới được tạo và xử lý cho mỗi yêu cầu) và ngữ cảnh đọc một thực thể thường được xử lý trước khi thực thể đó được sử dụng lại.
Bạn có thể tắt tính năng theo dõi các đối tượng thực thể trong bộ nhớ bằng cách gọi phương thức AsNoTracking
. Các tình huống điển hình mà bạn có thể muốn thực hiện điều đó bao gồm:
-
Trong suốt thời gian tồn tại của ngữ cảnh, bạn không cần cập nhật bất kỳ thực thể nào và bạn không cần EF tự động tải các property điều hướng với các thực thể được truy xuất bằng các truy vấn riêng biệt. Những điều kiện này thường được đáp ứng trong các phương thức hành động HttpGet của controller.
-
Bạn đang chạy một truy vấn truy xuất một lượng lớn dữ liệu và chỉ một phần nhỏ dữ liệu trả về sẽ được cập nhật. Có thể hiệu quả hơn nếu tắt theo dõi cho truy vấn lớn và chạy truy vấn sau cho một số thực thể cần được cập nhật.
-
Bạn muốn đính kèm một thực thể để cập nhật nó, nhưng trước đó bạn đã truy xuất cùng một thực thể đó cho một mục đích khác. Vì thực thể này đã được ngữ cảnh cơ sở dữ liệu theo dõi nên bạn không thể đính kèm thực thể mà bạn muốn thay đổi. Một cách để xử lý tình huống này là gọi
AsNoTracking
trên truy vấn trước đó.
Để biết thêm thông tin, hãy xem Tracking và No-Tracking.
Lấy mã
Tải xuống hoặc xem ứng dụng đã hoàn thành.
Bước tiếp theo
Trong hướng dẫn này, bạn:
- Tùy chỉnh trang Details
- Đã cập nhật trang Create
- Đã cập nhật trang Edit
- Đã cập nhật trang Delete
- Kết nối cơ sở dữ liệu đã đóng
Chuyển sang hướng dẫn tiếp theo để tìm hiểu cách mở rộng chức năng của trang Index bằng cách thêm các chức năng sắp xếp, lọc và phân trang.
Tiếp theo: Sắp xếp, lọc và phân trang