[ASP.NET for Beginner] - Part 4 - CRUD và Data Validation
Vậy là bạn đã có một khái niệm cơ bản về MVC, cách kết nối tới database, lựa chọn cho mình một front end phù hợp, đã tới lúc bắt tay vào thực hiện 4 thao tác cơ bản nhất của bất kỳ ứng dụng web nào: CRUD
Xem các bài viết trong series
1. CRUD
CRUD viết tắt cho 4 hành động liên quan tới database là Create
, Read
, Update
và Delete
Bài viết này giả định rằng bạn sử dụng Entity Framework, như đã nói ở phần 2, và đã có sẵn code trong ví dụ Bạn có thể clone code tại Github ở đây
1.1. DbContext
Trong ví dụ mvcbasic
, MvcBasicDbContext
đã được tạo sẵn cho bạn, và được khai báo ở mỗi Controller cần thiết. Ta sẽ sử dụng class này
Trong class PhoneController đã được tạo sẵn trong ví dụ, bạn có thể thấy cả 4 phương thức này được viết sẵn.
1.2. Create
Để tạo 1 record trong database, đối với Entity Framework, chuyện rất đơn giản
var phone = new Phone
{
Name = "Samsung Galaxy A5 (2017)"
};
_context.Phones.Add(phone);
_context.SaveChanges();
Trong trường hợp bạn muốn thêm nhiều dòng cùng một lúc, thì Entity Framework hỗ trợ một phương thức khác
var phones = new List<Phone>();
var phone = new Phone
{
Name = "Phone 1"
};
phones.Add(phone);
phone = new Phone
{
Name = "Phone 2"
};
phones.Add(phone);
_context.Phones.AddRange(phones);
_context.SaveChanges();
1.3. Read
Để lấy 1 dòng dữ liệu từ 1 bảng, bạn có thể dùng method Find
var id = 1;
var phone = _context.Phones.Find(id);
Method Find
sẽ tìm trong bảng Phone có khóa chính = id của bạn. Kết quả trả về null nếu khóa đó không tồn tại
Để lấy nhiều dòng dữ liệu thỏa một điều kiện nào đó, bạn có thể dùng LINQ
để query
var searchKey = "samsung";
var phones = _context.Phones.Where(x => x.Name.Contains(searchKey));
x
đại diện cho 1 object Phone trong database
x.Name.Contains(searchKey)
nghĩa là Name có chứa string searchKey
Hầu như tất cả các query bạn quen thuộc khi viết câu truy vấn bằng SQL đều có thể viết được dưới dạng LINQ. Đây gọi là LINQ to Entities (Dùng Linq để query thông qua các entities trong Entity Framework)
Bạn có thể tìm các câu lệnh tương ứng trong Linq bằng từ khóa: “How to hành động bạn muốn làm using linq”
Kết quả trả về của một truy vấn là kiểu dữ liệu IQueryable
. Bạn có thể tiếp tục lọc, gom nhóm, filter lại, hoặc chuyển đổi thành một kiểu dữ liệu khác như List, Array, etc
Bạn có thể xem thêm về các câu lệnh Linq được support ở đây: Supported and Unsupported LINQ Methods (LINQ to Entities) on docs.microsoft.com
1.4. Update
Không đơn giản như các lệnh khác, update dùng Entity Framework hơi phức tạp (hơi thôi)
1.4.1. The Simplest - 2 trip to database
Cách đơn giản nhất để update 1 record gồm 3 bước 1. Lấy record đó lên từ database - Truy cập database lần 1 2. Thay đổi 1 thông tin nào đó trong record 3. Update lại record đó trong database - Truy cập database lần 2
code của nó như sau
// get the phone object from database
var phone = _context.Phones.Find(phoneId);
// change some info
phone.Name = "Test"
// update the change to database
_context.SaveChanges();
Như bạn thấy, cách này bắt buộc bạn phải lấy 1 dữ liệu từ database lên trước khi bạn có thể edit nó
1.4.2. A more complicated
Vậy nếu bạn biết trước tất cả các dữ liệu của object thì sao? Trong trường hợp này, query record đó từ database là không cần thiết
// Create the object from your brain
var phone = new Phone
{
Id = 1
};
// Attach it to DbContext, so the DbContext can "track" the object
// Gắn nó vào DbContext, để DbContext có thể "theo dõi" object của bạn
_context.Phones.Attach(phone);
// Do some change
phone.Name = "Test";
// Save the change
_context.SaveChanges();
1.4.3. Track???
Entity Framework có 1 cách rất hay (và hơi phức tạp) để có thể tối ưu hóa các phương thức truy xuất/cập nhật dữ liệu: Nó sẽ theo dõi các thay đổi của 1 object
Giả sử, object của bạn có 50 field. Bạn chỉ thay đổi 1 field trong đó, rồi gọi _context.SaveChanges()
, EF sẽ biết được rằng chỉ có 1 field bị thay đổi, và nó sẽ cập nhật đúng 1 field duy nhất, tăng hiệu suất của ứng dụng.
Để làm được điều này, nó sẽ query object đó trước bằng khóa chính, và khi bạn gọi Attach, nó sẽ bắt đầu theo dõi object của bạn.
Khi bạn thay đổi 1 field, thì trạng thái của field đó từ Unmodified
sẽ chuyển thành Modified
1.5. Delete
Tương tự như edit, khi muốn delete 1 object, bạn cũng cần “2 chuyến” tới database
var phone = _context.Phones.Find(id);
_context.Phones.Remove(phone);
_context.SaveChanges();
2. DbContext - Một cách code tốt hơn
2.1. Nếu CRUD failed?
Sẽ có nhiều trường hợp, khi gọi các câu lệnh crud bằng entity framework, ko có dòng data nào được thêm vào hoặc sửa đổi. Vậy làm sao bạn biết khi nào thành công khi nào không?
_context.SaveChanges();
May mắn là method trên trả về số lượng record có thay đổi
Vì vậy, bạn có thể kiểm tra rất dễ bằng cách
// your code here to CRUD
var result = _context.SaveChanges();
bằng cách kiểm tra “result > 0”, bạn có thể biết được câu lệnh save changes của mình có thành công hay ko
2.2. await hay không?
Trong code controller được sinh ra bởi asp.net, bạn sẽ thấy các câu lệnh có dùng _context
đều có async
đằng sau, đằng trước là await
, và kiểu dữ liệu của method là Task
Hiểu một cách đơn giản, async - await là một cặp từ khóa giúp đơn giản hóa việc lập trình multi thread.
Nếu bạn chưa nắm vững kỹ thuật async-await, thì mình khuyên bạn là nên… bỏ hẳn và chỉ sử dụng các method trong các phần code bên trên trong bài viết này
3. Model Validation
Chắc hẳn bạn đã từng nghe các ràng buộc như
Tên không được chứa quá 20 ký tự Số điện thoại phải có 10 số
Bạn có tự hỏi “Họ đã làm điều đó như thế nào?”
3.1. DataAnnotation
ASP.NET cho phép bạn ràng buộc dữ liệu thông qua các DataAnnotation
được viết thêm trên đầu mỗi property
Lấy ví dụ với model Phone
, mình muốn thêm 1 ràng buộc là tên không được quá 50 ký tự, và báo lỗi khi user nhập quá số lượng
namespace mvcbasic.Models
{
using System.ComponentModel.DataAnnotations;
public class Phone
{
public int Id { get; set; }
[ ]
public string Name { get; set; }
}
}
Bạn có thể tìm hiểu sâu hơn về cách làm 1 câu error message chung cho tất cả các property cùng loại tại đây: Error Message – chung mà riêng
Để check một object có thỏa điều kiện của model ko, bạn có thể dùng ModelState
. Ví dụ trong method Create
// POST: Phone/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[ ]
[ ]
public async Task<IActionResult> Create([Bind("Id,Name")] Phone phone)
{
// CHECK MODEL STATE HERE
if (ModelState.IsValid)
{
_context.Add(phone);
await _context.SaveChangesAsync();
var phone2 = new Phone
{
Name = "Test"
};
var phones = new List<Phone>();
_context.Phones.AddRange(phones);
return RedirectToAction(nameof(Index));
}
return View(phone);
}
3.2. Client side validation
Client side validation cho phép user thấy các lỗi dữ liệu ngay khi họ nhập mà chưa cần gửi data lên server của bạn
Việc bạn cần làm là thêm các dòng sau vào code View của mình
<!--dòng này có thể đã nằm trong file View/Shared/_Layout.cshtml của bạn rồi-->
<a href="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.0.min.js">https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.0.min.js</a>
<a href="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js">https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js</a>
<a href="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js">https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js</a>
và trong form, bạn thêm đoạn code sau để hiển thị thông báo lỗi (nếu có)
<form asp-action="Create">
<!--dòng này hiển thị một thông báo tổng hợp tất cả các lỗi-->
<div class="text-danger"></div>
<div class="form-group">
<!--dòng này hiển thị thông báo lỗi cụ thể cho form-->
<span class="text-danger"></span>
</div>
<div class="form-group">
</div>
</form>
Lỗi hiển thị như sau
Bạn có thể xem toàn bộ các annotation ở đây: Data Annotation on docs.microsoft.com
Thế là xong. Các bạn đón chờ phần tiếp theo nhé