Professional Documents
Culture Documents
Cùng với sự phát triển nhanh chóng của khoa học - kỹ thuật trong những thập niên
gần đây, các ứng dụng công nghệ thông tin cũng trở nên phong phú, đa dạng và đóng
vai trò không thể thiếu trong sự phát triển chung của nền kinh tế.
Những chương trình ứng dụng, những phần mềm quản lý được thay thế cho việc
quản lý bằng hồ sơ, sổ sách đã quá lạc hậu, tốn chi phí và thời gian. Hơn thế nữa, tác
động của nó còn len lỏi vào tất cả các lĩnh vực nghiên cứu và đời sống. Từ những
website tin tức đến website thương mại, thanh toán điện tử, từ các mô hình thí nghiệm
ảo trên máy tính, những chương trình quản lý hoạt động của cả một hệ thống điều
khiển tự động đến chính phủ điện tử, tất cả đều là thành tựu vượt bậc của công nghệ
thông tin.
Đặc biệt, với sự phát triển không ngừng đó, các ứng dụng mới ngày càng đòi hỏi
những tính năng ưu việt hơn. Không những về tính linh hoạt, thời gian xử lý mà còn
phải kể đến khả năng lưu trữ lượng dữ liệu cực kỳ lớn cùng với tốc độ tìm kiếm, thực
thi lệnh nhanh chóng và hiệu quả. Để đáp ứng điều này, ngoài việc sử dụng hệ quản trị
cơ sở dữ liệu phù hợp, chúng ta cũng cần phải có các công cụ, môi trường tốt để phát
triển ứng dụng. Quan trọng hơn, cần phải biết cách tổ chức chương trình một cách
khoa học và tinh tế nhằm cải thiện hiệu suất và nâng cao chất lượng của phần mềm
ứng dụng.
Được sự động viên và đóng góp ý kiến của các bạn đồng nghiệp trong khoa Công
nghệ Thông tin, chúng tôi biên dịch và giới thiệu “Giáo trình lập trình cơ sở dữ liệu”
này tới các bạn sinh viên như một công cụ để phát triển cho mình những kỹ năng cần
thiết trong việc xây dựng các phần mềm ứng dụng. Lượng kiến thức là vô hạn, vì thế,
trong giáo trình này, chúng tôi không thể đề cập được hết những khía cạnh của việc
phát triển phần mềm. Thay vào đó, chúng tôi hi vọng, đây sẽ là tài liệu tham khảo
tương đối đầy đủ về lập trình ứng dụng với cơ sở dữ liệu để quý bạn đọc tự khám phá
và phát huy những kỹ năng tiềm ẩn của mình.
Giáo trình được chia làm 10 chương với nội dung như sau:
Chương 1 giới thiệu tổng quan và trình bày một số khái niệm liên quan về cơ sở dữ
liệu. Ngoài ra, chương này cũng hướng dẫn cách sử dụng các công cụ để quản lý, tạo
các lệnh SQL và truy xuất đến một cơ sở dữ liệu SQL Server.
Chương 2 trình bày tổng quan về ADO.Net, các lớp của ADO.Net và minh họa một
tính năng mạnh mẽ của ADO.Net là khả năng lưu trữ, xử lý các dòng lấy từ cơ sở dữ
liệu sau khi đã ngắt kết nối.
Chương 3 hướng dẫn chi tiết cách thiết lập một kết nối đến cơ sở dữ liệu. Mọi thao
tác xử lý có liên quan đến cơ sở dữ liệu phải thông qua một phương tiện liên lạc –
được gọi là đối tượng kết nối (Connection).
Chương 4 đi sâu phân tích các bước để thực thi các lệnh truy vấn cơ sở dữ liệu như
SELECT, INSERT, UPDATE và DELETE thông qua đối tượng Command. Ngoài ra,
bạn cũng sẽ được học cách dùng đối tượng Command để gọi một thủ tục, thực thi thủ
tục theo các tham số và lấy về các dòng, các cột trong một bảng hay một khung nhìn
cụ thể.
Chương 5 được dành trọn để giới thiệu về các lớp DataReader. Đây là một trong
những đối tượng thuộc nhóm lớp kết nối được dùng để đọc dữ liệu trả về từ cơ sở dữ
liệu thông qua đối tượng Command.
Chương 6 trình bày chi tiết cách sử dụng đối tượng DataSet – trong nhóm lớp
không kết nối – để lưu trữ dữ liệu nhận được từ cơ sở dữ liệu. DataSet cho phép lưu
trữ một bản sao các dòng của các bảng lấy được cơ sở dữ liệu. Bạn có thể làm việc với
dữ liệu trong DataSet ngay cả khi kết nối đã bị ngắt.
Chương 7 đi sâu tìm hiểu các tính năng cao cấp của DataSet để cập nhật các dòng
trong DataSet sau đó đồng bộ các thay đổi với cơ sở dữ liệu thông qua đối tượng trung
gian DataAdapter.
Chương 8 hướng dẫn cho bạn cách sử dụng đối tượng DataView để trích lọc và sắp
xếp dữ liệu theo những tiêu chí cụ thể. Ưu điểm chính của DataView là khả năng
chuyển tải dữ liệu lên các điều khiển trong ứng dụng Windows Form hoặc ASP.Net.
Chương 9 giới thiệu cách sử dụng các đối tượng ràng buộc (Constraint) và quan hệ
(Relation) để điều hướng và xử lý dữ liệu trong các bảng có quan hệ với nhau.
Chương 10 giúp bạn nghiên cứu cách điều khiển các giao dịch nâng cao sử dụng
SQL Server và ADO.Net nhằm nâng cao hiệu suất của sản phẩm phần mềm.
Chúng tôi hi vọng giáo trình này sẽ là một tài liệu tham khảo có ích đối với bạn
đọc. Chúng tôi cũng rất mong nhận được sự cổ vũ và những ý kiến đóng góp quý báu
của các bạn.
Cuối cùng, chúng tôi xin gửi lời cảm ơn đến các thầy cô và các bạn đồng nghiệp đã
ủng hộ và giúp đỡ chúng tôi hoàn thành giáo trình này.
MỤC LỤC
LỜI MỞ ĐẦU............................................................................................................ 2
MỤC LỤC ................................................................................................................. 4
Trang 4
Giáo trình lập trình cơ sở dữ liệu
Trang 5
Giáo trình lập trình cơ sở dữ liệu
Trang 6
Giáo trình lập trình cơ sở dữ liệu
Trang 7
Giáo trình lập trình cơ sở dữ liệu
Trang 8
Giáo trình lập trình cơ sở dữ liệu
10. CHƯƠNG 10: KIỂM SOÁT GIAO DỊCH NÂNG CAO ............................. 343
10.1. Lớp SqlTransaction.................................................................................... 343
10.2. Thiết lập điểm lưu (savepoint) ................................................................... 344
10.2.1. Thiết lập điểm lưu dùng T-SQL............................................................ 344
10.2.2. Tạo điểm lưu bằng đối tượng SqlTransaction ....................................... 346
10.3. Gán mức độ độc lập của giao dịch ............................................................. 348
10.3.1. Thiết lập mức độ độc lập giao dịch dùng T-SQL .................................. 350
10.3.2. Gán mức độ độc lập giao dịch qua đối tượng SqlTransaction................ 351
10.4. Tìm hiểu về các chế độ khóa của SQL Server ............................................ 356
10.4.1. Các loại khóa trong SQL Server ........................................................... 356
10.4.2. Các chế độ khóa ................................................................................... 356
10.4.3. Xem thông tin các khóa của SQL Server .............................................. 357
10.5. Chặn giao dịch – Transaction Blocking...................................................... 359
10.6. Đặt thời gian TimeOut cho khóa ................................................................ 360
10.7. Chặn và đọc các giao dịch Serializable / Repeatable .................................. 360
10.8. Deadlocks .................................................................................................. 364
10.9. Kết chương ................................................................................................ 369
Trang 9
Giáo trình lập trình cơ sở dữ liệu
Trang 10
Giáo trình lập trình cơ sở dữ liệu
1. CHƯƠNG 1
TỔNG QUAN VỀ CƠ SỞ DỮ LIỆU
Chương này giới thiệu các vấn đề căn bản về cơ sở dữ liệu: các cơ sở dữ liệu được
xây dựng như thế nào, cách tạo các bảng dữ liệu và quan hệ hay cách xây dựng các câu
lệnh truy vấn để trích rút thông tin, các giao dịch cơ sở dữ liệu là gì… Chương này
cũng giúp bạn tìm hiểu cách sử dụng hệ quản trị Microsoft SQL Server với cơ sở dữ
liệu minh họa Northwind. Đây là cơ sở dữ liệu chứa thông tin bán hàng của công ty
Northwind thường được cài đặt sẵn trong các phiên bản của Microsoft SQL Server.
Trang 11
Giáo trình lập trình cơ sở dữ liệu
Trang 12
Giáo trình lập trình cơ sở dữ liệu
Trang 13
Giáo trình lập trình cơ sở dữ liệu
Ta có thể quản lý các mối quan hệ của một bảng qua trình Enterprise Manager của
SQL Server bằng cách chọn bảng đó từ mục Tables, nhắp phải chuột và chọn Design
Table. Sau đó, nhấp chọn nút Manage Relationships trên thanh công cụ Table Deigner.
Hình 1.2 minh họa mối quan hệ giữa hai bảng Customers và Orders.
Hình này thể hiện các bảng Customers và Orders có quan hệ với nhau thông qua
cột CustomerID. Cột CustomerID trong bảng Orders là khóa ngoại. Quan hệ giữa hai
bảng có tên là FK_Orders_Customers.
Trang 14
Giáo trình lập trình cơ sở dữ liệu
giá trị). Nếu cố tình thêm một dòng nhưng không đặt giá trị cho cột not-null thì cơ sở
dữ liệu sẽ phát sinh một lỗi và dòng mới không được thêm vào bảng.
Trang 15
Giáo trình lập trình cơ sở dữ liệu
Lưu ý: SQL Server tự động tạo chỉ mục cho cột chứa khóa chính của bảng.
Thông thường, người quản trị cơ sở dữ liệu (DBA) chịu trách nhiệm tạo chỉ mục.
Tuy nhiên, với tư cách là người phát triển ứng dụng, bạn biết rõ hơn về ứng dụng của
mình và nên đề nghị cột nào cần được làm chỉ mục.
Ta có thể quản lý chỉ mục cho một bảng bởi Enterprise Manager bằng cách chọn
bảng từ mục Tables, nhấp phải chuột và chọn All Tasks Manage Indexes. Ví dụ,
Hình 1.3 cho thấy các chỉ mục của bảng Customers. Bạn củng có thể quản lý các chỉ
mục của bảng từ màn hình thiết kế bảng bằng cách nhấn nút Manage Indexes/Keys.
Bảng Customers có 5 chỉ mục trên các cột CustomerID, City, CompanyName,
PostalCode và Region.
Trang 16
Giáo trình lập trình cơ sở dữ liệu
TYPE DESCRIPTION
nvarchar Kiểu (chuỗi) kí tự Unicode chiều dài thay đổi với chiều dài
tối đa là 4000 kí tự.
ntext Kiểu (chuỗi) kí tự Unicode chiều dài thay đổi với chiều dài
tối đa là 230-1 (1,073,741,823) ký tự.
binary Kiểu dữ liệu nhị phân có chiều dài không đổi, tối đa 8000
bytes
varbinary Kiểu dữ liệu nhị phân có chiều dài thay đổi, tối đa 8000
bytes
image Kiểu dữ liệu nhị phân có chiều dài thay đổi, tối đa 231-1
(2,147,483,647) bytes.
cursor Kiểu con trỏ, tham chiếu tới một tập các mẫu tin
sql_variant Có thể lưu trữ các giá trị với kiểu dữ liệu khác nhau ngoại
trừ text, ntext, timestamp và sql_variant.
table Lưu trữ một tập các dòng.
timestamp Số nhị phân duy nhất được cập nhật mỗi khi bạn thay đổi một
dòng. Chỉ có thể định nghĩa một cột có kiểu timestamp trong
mỗi bảng.
uniqueidentifier Kiểu định danh duy nhất (GUID - Globally unique
identifier).
Bảng 1.2. Các kiểu dữ liệu trong SQL Server
Trang 17
Giáo trình lập trình cơ sở dữ liệu
Điều cần lưu ý đầu tiên đối với câu lệnh này là các thành phần trong câu lệnh
SELECT nếu được sử dụng phải tuân theo đúng thứ tự như trong cú pháp. Nếu không,
câu lệnh sẽ được xem là không hợp lệ.
Câu lệnh SELECT được sử dụng để tác động lên các bảng dữ liệu và kết quả của
câu lệnh cũng được hiển thị dưới dạng bảng, tức là một tập hợp các dòng và các cột
(ngoại trừ trường hợp sử dụng câu lệnh SELECT với mệnh đề COMPUTE).
Mệnh đề FROM trong câu lệnh SELECT được sử dụng nhằm chỉ định các bảng và
khung nhìn cần truy xuất dữ liệu. Sau FROM là danh sách tên của các bảng và khung
nhìn tham gia vào truy vấn, tên của các bảng và khung nhìn được phân cách nhau bởi
dấu phẩy.
Mệnh đề WHERE trong câu lệnh SELECT được sử dụng nhằm xác định các điều
kiện đối với việc truy xuất dữ liệu. Sau mệnh đề WHERE là một biểu thức logic và chỉ
những dòng dữ liệu nào thoả mãn điều kiện được chỉ định mới được hiển thị trong kết
quả truy vấn.
Trong mệnh đề WHERE thường sử dụng:
Các toán tử kết hợp điều kiện (AND, OR)
Các toán tử so sánh
Trang 18
Giáo trình lập trình cơ sở dữ liệu
Sau ORDER BY là danh sách các cột cần sắp xếp (tối đa là 16 cột). Dữ liệu được
sắp xếp có thể theo chiều tăng (ASC) hoặc giảm (DESC), mặc định là sắp xếp theo
chiều tăng. Nếu sau ORDER BY có nhiều cột thì việc sắp xếp dữ liệu sẽ được ưu tiên
theo thứ tự từ trái qua phải.
Mệnh đề GROUP BY sử dụng trong câu lệnh SELECT nhằm phân hoạch các dòng
dữ liệu trong bảng thành các nhóm dữ liệu, và trên mỗi nhóm dữ liệu thực hiện tính
toán các giá trị thống kê như tính tổng, tính giá trị trung bình,...
Các hàm gộp được sử dụng để tính giá trị thống kê cho toàn bảng hoặc trên mỗi
nhóm dữ liệu. Chúng có thể được sử dụng như là các cột trong danh sách chọn của câu
lệnh SELECT hoặc xuất hiện trong mệnh đề HAVING, nhưng không được phép xuất
hiện trong mệnh đề WHERE
SQL cung cấp các hàm gộp dưới đây:
Trang 19
Giáo trình lập trình cơ sở dữ liệu
Mệnh đề HAVING được sử dụng nhằm chỉ định điều kiện đối với các giá trị thống
kê được sản sinh từ các hàm gộp tương tự như cách thức mệnh đề WHERE thiết lập
các điều kiện cho câu lệnh SELECT. Mệnh đề HAVING thường không thực sự có
nghĩa nếu như không sử dụng kết hợp với mệnh đề GROUP BY. Một điểm khác biệt
giữa HAVING và WHERE là trong điều kiện của WHERE không được có các hàm
gộp trong khi HAVING lại cho phép sử dụng các hàm gộp trong điều kiện của mình.
Mệnh đề COMPUTE sử dụng kết hợp với các hàm gộp (dòng) và ORDER BY
trong câu lệnh SELECT cũng cho chúng ta các kết quả thống kê (của hàm gộp) trên
các nhóm dữ liệu. Điểm khác biệt giữa COMPUTE và GROUP BY là kết quả thống kê
xuất hiện dưới dạng một dòng trong kết quả truy vấn và còn cho chúng ta cả chi tiết về
dữ liệu trong mỗi nhóm. Như vậy, câu lệnh SELECT với COMPUTE cho chúng ta cả
chi tiết dữ liệu và giá trị thống kê trên mỗi nhóm.
Mệnh đề COMPUTE …BY có cú pháp như sau:
COMPUTE hàm_gộp(tên_cột) [,…, hàm_gộp (tên_cột)]
BY danh_sách_cột
Trong đó:
Các hàm gộp có thể sử dụng bao gồm SUM, AVG, MIN, MAX và COUNT.
danh_sách_cột: là danh sách cột sử dụng để phân nhóm dữ liệu
Khi sử dụng mệnh đề COMPUTE ... BY cần tuân theo các qui tắc dưới đây:
Từ khóa DISTINCT không cho phép sử dụng với các hàm gộp dòng
Hàm COUNT(*) không được sử dụng trong COMPUTE.
Sau COMPUTE có thể sử dụng nhiều hàm gộp, khi đó các hàm phải phân cách
nhau bởi dấu phẩy.
Trang 20
Giáo trình lập trình cơ sở dữ liệu
Các cột sử dụng trong các hàm gộp xuất hiện trong mệnh đề COMPUTE phải
có mặt trong danh sách chọn.
Không sử dụng SELECT INTO trong một câu lệnh SELECT có sử dụng
COMPUTE.
Nếu sử dụng mệnh đề COMPUTE ... BY thì cũng phải sử dụng mệnh đề
ORDER BY. Các cột liệt kê trong COMPUTE … BY phải giống hệt hay là một
tập con của những gì được liệt kê sau ORDER BY. Chúng phải có cùng thứ tự
từ trái qua phải, bắt đầu với cùng một biểu thức và không bỏ qua bất kỳ một
biểu thức nào.
Dạng đơn giản nhất cho phép bạn liệt kê danh sách các cột muốn lấy dữ liệu từ một
bảng. Chẳng hạn, câu lệnh SELECT sau lấy dữ liệu từ các cột CustomerID,
CompanyName, ContactName và Address của bảng Customers.
SELECT CustomerID, CompanyName, ContactName, Address
FROM Customers;
Các cột cần lấy dữ liệu được liệt kê ngay sau từ khóa SELECT và tên bảng chứa dữ
liệu muốn lấy được đặt ngay sau từ khóa FROM. Nếu muốn lấy dữ liệu trên tất cả các
cột của một bảng, chỉ cần dùng dấu * ngay sau từ khóa SELECT thay vì phải liệt kê tất
cả các cột.
Hình 1.4. Kết quả truy vấn của một lệnh SELECT
Để lấy dữ liệu từ một cột, một bảng mà tên của nó có chứa khoảng trắng, cần phải
đặt tên cột, tên bảng trong cặp dấu móc vuông [ ]. Chẳng hạn:
SELECT * FROM [Order Details];
Ví dụ 1: Câu lệnh SELECT sau sử dụng mệnh đề WHERE để lấy ra các khách hàng có
giá trị trên cột Country là ‘UK’.
SELECT CustomerID, CompanyName, City
Trang 21
Giáo trình lập trình cơ sở dữ liệu
Ví dụ 2: Câu lệnh SELECT sau sử dụng mệnh đề WHERE để tìm thông tin chi tiết của
một sản phẩm có mã số (ProductID) là 10.
Toán tử bằng “=” không phải là toán tử duy nhất có thể sử dụng trong mệnh đề
WHERE. Ngoài nó ra, ta có thể dùng các toán tử kết hợp điều kiện hay các toán tử so
sánh…
Ví dụ 3: Câu lệnh SELECT sau sử dụng toán tử “<=” để lấy các dòng trong bảng
Products với điều kiện mã sản phẩm (ProductID) nhỏ hơn hoặc bằng 10.
SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice
FROM Products
WHERE ProductID <= 10;
Ví dụ 4: Câu lệnh SELECT sau sử dụng toán tử “<>” để lấy các dòng trong bảng
Products với điều kiện mã sản phẩm khác 10.
SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice
FROM Products
WHERE ProductID <> 10;
Ví dụ 5: Câu lệnh SELECT sau sử dụng toán tử LIKE để lấy các sản phẩm có tên gồm
4 ký tự và bắt đầu bằng ‘Cha’.
Trang 22
Giáo trình lập trình cơ sở dữ liệu
Hình 1.6. Các sản phẩm có tên gồm 4 ký tự và bắt đầu bằng ‘Cha’
Ví dụ 6: Câu lệnh SELECT sau sử dụng toán tử LIKE để lấy các sản phẩm có tên bắt
đầu bằng ‘Cha’.
SELECT ProductID, ProductName
FROM Products
WHERE ProductName LIKE 'Cha%';
Hình 1.7. Các sản phẩm có tên bắt đầu bằng ‘Cha’
Ví dụ 7: Câu lệnh SELECT sau sử dụng toán tử LIKE để lấy danh sách các sản phẩm
có tên bắt đầu bởi A, B hoặc C.
SELECT ProductID, ProductName
FROM Products WHERE ProductName LIKE '[ABC]%';
Trang 23
Giáo trình lập trình cơ sở dữ liệu
Hình 1.8. Các sản phẩm có tên bắt đầu bởi A, B hoặc C.
Ví dụ 8: Liệt kê các sản phẩm có tên bắt đầu bởi các chữ cái khác A, B và C
SELECT ProductID, ProductName
FROM Products WHERE ProductName LIKE '[^ABC]%';
Ví dụ 9: Liệt kê các sản phẩm có tên bắt đầu bởi các chữ cái từ A đến E
SELECT ProductID, ProductName
FROM Products WHERE ProductName LIKE '[A-E]%';
Ví dụ 10: Lệnh SELECT sau sử dụng toán tử IN để lấy các sản phẩm có mã sản phẩm
(ProductID) nằm trong các giá trị sau: 1, 2, 5, 15, 20, 45, 50.
Ví dụ 11: Lấy danh sách mã đơn đặt hàng của khách hàng thuộc công ty có tên bắt đầu
bằng ‘Fu’.
SELECT OrderID
FROM Orders
WHERE CustomerID IN (
SELECT CustomerID
FROM Customers
WHERE CompanyName LIKE 'Fu%'
);
Lưu ý, trong câu lệnh trên, có hai mệnh đề SELECT được lồng vào nhau. Dạng
truy vấn như vậy được gọi là truy vấn lồng hay truy vấn con. Để lấy được danh sách
mã đơn đặt hàng, trước hết, phải dùng một câu lệnh SELECT để tìm ra mã khách hàng
có tên công ty bắt đầu bằng ‘Fu’.
Trang 24
Giáo trình lập trình cơ sở dữ liệu
Trong câu lệnh INSERT, danh sách cột ngay sau tên bảng không cần thiết phải chỉ
định nếu giá trị các trường của bản ghi mới được chỉ định đầy đủ trong danh sách trị.
Trong trường hợp này, số lượng các giá trị trong danh sách trị phải bằng với số lượng
các trường của bảng cần bổ sung dữ liệu cũng như phải tuân theo đúng thứ tự của các
trường như khi bảng được định nghĩa.
Một cách sử dụng khác của câu lệnh INSERT được sử dụng để bổ sung nhiều dòng
dữ liệu vào một bảng, các dòng dữ liệu này được lấy từ một bảng khác thông qua câu
lệnh SELECT. Ở cách này, các giá trị dữ liệu được bổ sung vào bảng không được chỉ
định tường minh mà thay vào đó là một câu lệnh SELECT truy vấn dữ liệu từ bảng
khác.
Cú pháp câu lệnh INSERT có dạng như sau:
INSERT INTO tên_bảng[(danh_sách_cột)] câu_lệnh_SELECT
Khi bổ sung dữ liệu theo cách này cần lưu ý một số điểm sau:
Kết quả của câu lệnh SELECT phải có số cột bằng với số cột được chỉ định
trong bảng đích và phải tương thích về kiểu dữ liệu.
Trong câu lệnh SELECT được sử dụng mệnh đề COMPUTE ... BY
Ví dụ 2: Thêm một dòng mới vào bảng Customers (không liệt kê các cột)
Trang 25
Giáo trình lập trình cơ sở dữ liệu
Ví dụ 3: Giả sử ta có một bảng dự phòng Custs để lưu trữ thông tin CustomerID,
CompanyName, ContactName và Address của khách hàng ở USA. Để lấy thông tin từ
bảng Customers để thêm vào bảng này, ta viết như sau:
Sau UPDATE là tên của bảng cần cập nhật dữ liệu. Một câu lệnh UPDATE có thể
cập nhật dữ liệu cho nhiều cột bằng cách chỉ định các danh sách tên cột và biểu thức
tương ứng sau từ khoá SET. Mệnh đề WHERE trong câu lệnh UPDATE thường được
sử dụng để chỉ định các dòng dữ liệu chịu tác động của câu lệnh (nếu không chỉ định,
phạm vi tác động của câu lệnh được hiểu là toàn bộ các dòng trong bảng)
Ví dụ 1: Cập nhật địa chỉ mới cho khách hàng có mã JPCOM
UPDATE Customers
SET Address = '3 North Street'
WHERE CustomerID = 'JPCOM';
Ví dụ 2: Cập nhật địa chỉ và chức danh mới cho khách hàng có mã JPCOM
UPDATE Customers
SET Address = '5 Liberty Street', ContactTitle = 'CEO'
WHERE CustomerID = 'JPCOM';
Trang 26
Giáo trình lập trình cơ sở dữ liệu
Trong câu lệnh này, tên của bảng cần xoá dữ liệu được chỉ định sau DELETE
FROM. Mệnh đề WHERE trong câu lệnh được sử dụng để chỉ định điều kiện đối với
các dòng dữ liệu cần xoá. Nếu câu lệnh DELETE không có mệnh đề WHERE thì toàn
bộ các dòng dữ liệu trong bảng đều bị xoá.
Ví dụ: Xóa khách hàng có mã là CRCOM khỏi bảng Customers
Ví dụ 1: Chẳng hạn, xét trường hợp muốn thêm một dòng mới vào bảng nhưng giá trị
trên cột khóa chính đã tồn tại. Lệnh INSERT sau tìm cách thêm một dòng mới vào
bảng Customers với CustomerID là ALFKI.
Nếu tiếp tục thực thi câu lệnh INSERT này, cơ sở dữ liệu phát sinh một thông báo lỗi
với nội dung như sau:
Lệnh này xảy ra lỗi vì đã có một dòng trong bảng Customers chứa giá trị trên cột
khóa chính là ALFKI. Thông báo này chỉ cho ta biết giá trị được cung cấp cho cột
khóa chính trong lệnh INSERT đã tồn tại trong bảng Customers. Trong đó, tên ràng
buộc PK_Customers là tên của ràng buộc trên bảng được gán cho cột khóa chính khi
bảng Customers được tạo. Cuối cùng, thông báo lỗi cho biết lệnh đã bị hủy.
Ví dụ 2: Xét trường hợp thay đổi giá trị trên cột khóa chính của một bảng cha và giá trị
hiện tại đang được tham chiếu bởi một khóa ngoại trong bảng con. Lệnh UPDATE sau
thực hiện việc cập nhật giá trị trên cột CustomerID từ ALFKI sang ALFKZ trong bảng
Customers. Dòng chứa thông tin cho khách hàng này được tham chiếu bởi các dòng
trong bảng Orders.
Trang 27
Giáo trình lập trình cơ sở dữ liệu
UPDATE Customers
SET CustomerID = 'ALFKZ'
WHERE CustomerID = 'ALFKI';
Nếu thực thi câu lệnh UPDATE này, ta sẽ nhận được thông báo lỗi:
Lệnh UPDATE này bị lỗi do hàng chứa giá trị trên cột khóa chính ALFKI được
tham chiếu bởi một số dòng trong bảng Orders. Thông báo lỗi này còn cho biết giá trị
mới trên cột CustomerID vi phạm ràng buộc khóa ngoại trên cột CustomerID của bảng
Orders. Ràng buộc này có tên FK_Orders_Customers.
Tương tự, bạn không thể xóa một dòng trong bảng cha khi có một dòng trong bảng
con tham chiếu tới nó. Chẳng hạn, khi thực hiện lệnh DELETE sau:
Bạn sẽ nhận được một thông báo lỗi tương tự. Lệnh DELETE này bị lỗi vì bảng
Orders chứa các dòng tham chiếu đến dòng bị xóa trong bảng Customers. Việc xóa
dòng này làm cho cơ sở dữ liệu mất tính nhất quán vì các dòng trong bảng Orders
không còn tham chiếu đến một dòng hợp lệ.
Trang 28
Giáo trình lập trình cơ sở dữ liệu
Để bắt đầu giao dịch, ta dùng lệnh BEGIN TRANSACTION hoặc viết tắt là
BEGIN TRANS. Sau đó, thực hiện các lệnh SQL đã tạo trong giao dịch. Để xác nhận
giao dịch, ta thực hiện lệnh COMMIT TRANSACTION hoặc COMMIT TRANS hay
COMMIT. Để quay ngược giao dịch để hủy bỏ các lệnh, ta gọi lệnh ROLLBACK
TRANSACTION hoặc ROLLBACK TRANS hay ROLLBACK.
Theo mặc định, giao dịch sẽ bị hủy nếu không thực hiện lệnh COMMIT để xác
nhận các thay đổi.
Ví dụ: Giao dịch sau chứa hai lệnh INSERT: lệnh đầu tiên thêm một dòng mới vào
bảng Customers và lệnh thứ hai thêm một dòng mới vào bảng Orders. Cuối cùng, gọi
lệnh COMMIT để xác nhận giao dịch.
BEGIN TRANSACTION;
COMMIT;
Giao dịch tiếp theo chứa các lệnh INSERT tương tự như trên nhưng bây giờ, thay vì
dùng COMMIT, ta dùng ROLLBACK để hủy các thay đổi:
BEGIN TRANSACTION;
ROLLBACK;
Vì giao dịch bị hủy nên hai dòng được thêm vào bởi các lệnh INSERT cũng bị xóa
theo. Bạn có thể kiểm tra các lỗi trong giao dịch trước khi quyết định thực hiện lệnh
COMMIT hay ROLLBACK bằng cách dùng hàm @@ERROR. Hàm này trả về giá trị
0 nếu một lệnh được thực thi và không gây ra lỗi. Nếu @@ERROR trả về giá trị khác
0 nghĩa là đã có một lỗi đã xảy ra. Từ đó, ta thực hiện lệnh COMMIT khi @@ERROR
= 0 và ngược lại.
Bạn cũng có thể gán tên cho giao dịch trong lệnh BEGIN TRANSACTION. Điều
này rất hữu ích khi muốn biết giao dịch nào đang được thực hiện.
Trang 29
Giáo trình lập trình cơ sở dữ liệu
IF @@Error = 0
COMMIT TRANSACTION MyTransaction;
ELSE
ROLLBACK TRANSACTION MyTransaction;
Ví dụ 1: Tạo bảng
Giả sử, bạn muốn lưu trữ thông tin chi tiết về một số người trong cơ sở dữ liệu. Các
thông tin cần lưu trữ gồm Tên, Họ và Ngày Sinh. Ta đặt tên bảng là Persons. Để xác
định duy nhất một dòng trong bảng Persons, ta thêm một thuộc tính ID có kiểu số,
đóng vai trò khóa chính của bảng. Lệnh CREATE TABLE tạo bảng Persons theo mô
tả này như sau:
Mệnh đề CONSTRAINT dùng để giới hạn các giá trị được lưu trong một bảng hay
một cột. Mệnh đề này cũng được dùng để xác định khóa chính cho bảng bằng cách
dùng thêm từ khóa PRIMARY KEY. Khóa chính của bảng Persons là cột PersonID và
ràng buộc này được đặt tên là PK_Persons. Cột ID có kiểu số nguyên và mỗi giá trị
trên cột này ở mỗi dòng là một số khác nhau.
Các cột FirstName và LastName có kiểu chuỗi (nvarchar) lưu trữ tối đa 15 ký tự.
Cả hai cột này đều được định nghĩa kèm một ràng buộc NOT NULL. Nghĩa là bạn
phải cung cấp giá trị cho các cột này khi sử dụng lệnh INSERT để thêm một dòng mới
vào bảng. Mặc định, khi tạo cột cho bảng, nó được phép chứa giá trị NULL.
Trang 30
Giáo trình lập trình cơ sở dữ liệu
Thêm một cột EmployerID vào bảng Persons để lưu ID của công ty mà nhân viên làm
việc. Cột này là khóa ngoại, tham chiếu đến cột CustomerID trong bảng Customers.
Ràng buộc này có tên là FK_Persons_Customers.
Nói chung, nên tạo chỉ mục trên một cột chỉ khi việc tìm kiếm dữ liệu sẽ trả về một
số ít dòng từ một bảng chứa rất nhiều dòng. Một quy tắc hay là chỉ mục có ích khi bạn
muốn kết quả truy vấn nhận không quá 10% tổng số hàng trong một bảng. Điều này có
nghĩa là cột cần tạo chỉ mục là cột lưu trữ miền giá trị lớn.
Thông thường, người quản trị cơ sở dữ liệu sẽ chịu trách nhiệm tạo chỉ mục. Tuy
nhiên, với tư cách là lập trình viên, bạn biết nhiều hơn về ứng dụng của mình và có thể
yêu cầu cột nào trong bảng nên được tạo chỉ mục.
Trang 31
Giáo trình lập trình cơ sở dữ liệu
Câu lệnh CREATE VIEW được sử dụng để tạo ra khung nhìn và có cú pháp như sau:
Nếu trong câu lệnh CREATE VIEW, ta không chỉ định danh sách các tên cột cho
khung nhìn, tên các cột trong khung nhìn sẽ chính là tiêu đề các cột trong kết quả của
câu lệnh SELECT. Trong trường hợp tên các cột của khung nhìn đươc chỉ định, chúng
phải có cùng số lượng với số lượng cột trong kết quả của câu truy vấn.
Khi tạo khung nhìn với câu lệnh CREATE VIEW, ta cần lưu ý một số nguyên tắc sau:
Tên khung nhìn và tên cột trong khung nhìn, cũng giống như bảng, phải tuân
theo qui tắc định danh.
Không thể qui định ràng buộc và tạo chỉ mục cho khung nhìn.
Câu lệnh SELECT với mệnh đề COMPUTE ... BY không được sử dụng để định
nghĩa khung nhìn.
Phải đặt tên cho các cột của khung nhìn trong các trường hợp sau đây:
Trang 32
Giáo trình lập trình cơ sở dữ liệu
o Trong kết quả của câu lệnh SELECT có ít nhất một cột được sinh ra bởi
một biểu thức (tức là không phải là một tên cột trong bảng cơ sở) và cột
đó không được đặt tiêu đề.
o Tồn tại hai cột trong kết quả của câu lệnh SELECT có cùng tiêu đề cột.
Ví dụ: Câu lệnh sau tạo một khung nhìn chứa các thông tin về mã, tên, đơn vị tính,
nhóm sản phẩm và tên nhà cung cấp. Dữ liệu này được truy vấn và kết hợp từ 3 bảng
Products, Categories và Suppliers.
Trang 33
Giáo trình lập trình cơ sở dữ liệu
Thủ tục lưu trữ cho phép chúng ta thực hiện cùng một yêu cầu bằng một câu
lệnh đơn giản thay vì phải sử dụng nhiều dòng lệnh SQL. Điều này sẽ làm giảm
thiểu sự lưu thông trên mạng.
Thay vì cấp phát quyền trực tiếp cho người sử dụng trên các câu lệnh SQL và
trên các đối tượng cơ sở dữ liệu, ta có thể cấp phát quyền cho người sử dụng
thông qua các thủ tục lưu trữ, nhờ đó tăng khả năng bảo mật đối với hệ thống.
Thủ tục lưu trữ được tạo bởi câu lệnh CREATE PROCEDURE với cú pháp như sau:
CREATE PROCEDURE tên_thủ_tục [ (danh_sách_tham_số) ]
[ WITH RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION ]
AS
Các_câu_lệnh_của_thủ_tục
Trong đó:
tên_thủ_tục: Tên của thủ tục cần tạo. Tên phải tuân theo qui tắc định danh và
không được vượt quá 128 ký tự.
danh_sách_tham_số: Các tham số của thủ tục được khai báo ngay sau tên thủ
tục và nếu thủ tục có nhiều tham số thì các khai báo phân cách nhau bởi dấu
phẩy. Khai báo của mỗi một tham số tối thiểu phải bao gồm hai phần: tên tham
số được bắt đầu bởi dấu @ và kiểu dữ liệu của tham số. Ví dụ: @ProductID int
RECOMPILE: Thông thường, thủ tục sẽ được phân tích, tối ưu và dịch sẵn ở
lần gọi đầu tiên. Nếu tuỳ chọn WITH RECOMPILE được chỉ định, thủ tục sẽ
được dịch lại mỗi khi được gọi.
ENCRYPTION: Thủ tục sẽ được mã hoá nếu tuỳ chọn WITH ENCRYPTION
được chỉ định. Nếu thủ tục đã được mã hoá, ta không thể xem được nội dung
của thủ tục.
Các_câu_lệnh_của_thủ_tục: Tập hợp các câu lệnh sử dụng trong nội dung thủ
tục. Các câu lệnh này có thể đặt trong cặp từ khoá BEGIN...END hoặc có thể
không.
Ví dụ: Tạo thủ tục để thêm một sản phẩm vào bảng Products trong cơ sở dữ liệu
Northwind, sau đó lấy về mã của sản phẩm.
Trang 34
Giáo trình lập trình cơ sở dữ liệu
Khi một thủ tục lưu trữ đã được tạo ra, ta có thể yêu cầu hệ quản trị cơ sở dữ liệu
thực thi thủ tục bằng lời gọi thủ tục có dạng:
Tên_thủ_tục [ danh_sách_các_đối_số ]
Số lượng các đối số cũng như thứ tự của chúng phải phù hợp với số lượng và thứ tự
của các tham số khi định nghĩa thủ tục.
Trong trường hợp lời gọi thủ tục được thực hiện bên trong một thủ tục khác, bên
trong một trigger hay kết hợp với các câu lệnh SQL khác, ta sử dụng cú pháp như sau:
EXECUTE Tên_thủ_tục [ danh_sách_các_đối_số ]
Ví dụ: Tạo hàm tính tiền giảm giá cho một sản phẩm
Trang 35
Giáo trình lập trình cơ sở dữ liệu
Một hàm khi đã được định nghĩa, nó có thể được sử dụng như các hàm do hệ quản
trị cơ sở dữ liệu cung cấp (thông thường trước tên hàm ta phải chỉ định thêm tên của
người sở hữu hàm)
Ví dụ:
-- Tính tiền giảm với UnitPrice = 10 và Discount = 0.3
SELECT dbo.DiscountPrice(10, 0.3);
-- Tương tự
DECLARE @MyDiscountFactor float
SET @MyDiscountFactor = 0.3
SELECT dbo.DiscountPrice(UnitPrice, @MyDiscountFactor), UnitPrice
FROM Products
WHERE ProductID = 1;
Cú pháp của hàm nội tuyến phải tuân theo các qui tắc sau:
Kiểu trả về của hàm phải được chỉ định bởi mệnh đề RETURNS TABLE.
Trong phần thân của hàm chỉ có duy nhất một câu lệnh RETURN xác định giá
trị trả về của hàm thông qua duy nhất một câu lệnh SELECT. Ngoài ra, không
sử dụng bất kỳ câu lệnh nào khác trong phần thân của hàm.
Trang 36
Giáo trình lập trình cơ sở dữ liệu
Ví dụ: Tạo hàm trả về bảng dữ liệu chứa các dòng lấy từ bảng Products sao cho giá trị
trên cột UnitsInStock có giá trị bé hơn một số nguyên nào đó. Số này được truyền qua
tham số của hàm.
Với hàm được định nghĩa ở trên, để biết danh sách các sản phẩm có UnitsInStock <=
10, ta sử dụng câu lệnh như sau:
SELECT *
FROM ProductsToBeReordered(10);
Đối với hàm nội tuyến, phần thân của hàm chỉ cho phép sự xuất hiện duy nhất của
câu lệnh RETURN. Trong trường hợp cần phải sử dụng đến nhiều câu lệnh trong phần
thân của hàm, ta sử dụng cú pháp như sau để định nghĩa hàm:
Khi định nghĩa hàm dạng này cần lưu ý một số điểm sau:
Cấu trúc của bảng trả về bởi hàm được xác định dựa vào định nghĩa của bảng
trong mệnh đề RETURNS. Biến @biến_bảng trong mệnh đề RETURNS có
phạm vi sử dụng trong hàm và được sử dụng như là một tên bảng.
Câu lệnh RETURN trong thân hàm không chỉ định giá trị trả về. Giá trị trả về
của hàm chính là các dòng dữ liệu trong bảng có tên là @biếnbảng được định
nghĩa trong mệnh đề RETURNS
Cũng tương tự như hàm nội tuyến, dạng hàm này cũng được sử dụng trong các câu
lệnh SQL với vai trò như bảng hay khung nhìn. Ví dụ dưới đây minh hoạ cách sử dụng
dạng hàm này trong SQL.
Trang 37
Giáo trình lập trình cơ sở dữ liệu
ProductID int,
ProductName nvarchar(40),
UnitsInStock smallint,
Reorder nvarchar(3)
)
AS
BEGIN
RETURN
END
Với hàm được định nghĩa như trên, để lấy danh sách các sản phẩm có UnitsInStock
nhỏ hơn hoặc bằng một giá trị nào đó (chẳng hạn: 20), ta viết:
1.3.5.3. Trigger
Như ta đã biết, các ràng buộc được sử dụng để đảm bảo tính toàn vẹn dữ liệu trong
cơ sở dữ liệu. Một đối tượng khác cũng thường được sử dụng trong các cơ sở dữ liệu
cũng với mục đích này là các trigger. Cũng tương tự như thủ tục lưu trữ, một trigger là
một đối tượng chứa một tập các câu lệnh SQL và tập các câu lệnh này sẽ được thực thi
khi trigger được gọi. Điểm khác biệt giữa thủ tục lưu trữ và trigger là: các thủ tục lưu
trữ được thực thi khi người sử dụng có lời gọi đến chúng còn các trigger lại được “gọi”
tự động khi xảy ra những thao tác làm thay đổi dữ liệu trong các bảng.
Mỗi một trigger được tạo ra và gắn liền với một bảng nào đó trong cơ sở dữ liệu.
Khi dữ liệu trong bảng bị thay đổi (tức là khi bảng chịu tác động của các câu lệnh
INSERT, UPDATE hay DELETE) thì trigger sẽ được tự đông kích hoạt.
Sử dụng trigger một cách hợp lý trong cơ sở dữ liệu sẽ có tác động rất lớn trong
việc tăng hiệu năng của cơ sở dữ liệu. Các trigger thực sự hữu dụng với những khả
năng sau:
Một trigger có thể nhận biết, ngăn chặn và huỷ bỏ được những thao tác làm
thay đổi trái phép dữ liệu trong cơ sở dữ liệu.
Trang 38
Giáo trình lập trình cơ sở dữ liệu
Các thao tác trên dữ liệu (xoá, cập nhật và bổ sung) có thể được trigger phát
hiện ra và tự động thực hiện một loạt các thao tác khác trên cơ sở dữ liệu nhằm
đảm bảo tính hợp lệ của dữ liệu.
Thông qua trigger, ta có thể tạo và kiểm tra được những mối quan hệ phức tạp
hơn giữa các bảng trong cơ sở dữ liệu mà bản thân các ràng buộc không thể
thực hiện được.
Một trigger là một đối tượng gắn liền với một bảng và được tự động kích hoạt khi
xảy ra những giao tác làm thay đổi dữ liệu trong bảng. Định nghĩa một trigger bao
gồm các yếu tố sau:
Trigger sẽ được áp dụng đối với bảng nào?
Trigger được kích hoạt khi câu lệnh nào được thực thi trên bảng: INSERT,
UPDATE, DELETE?
Trigger sẽ làm gì khi được kích hoạt?
Câu lệnh CREATE TRIGGER được sử dụng để đinh nghĩa trigger và có cú pháp
như sau:
CREATE TRIGGER tên_trigger
ON tên_bảng
FOR {[INSERT][,][UPDATE][,][DELETE]}
AS
[IF UPDATE(tên_cột)
[AND UPDATE(tên_cột)|OR UPDATE(tên_cột)]
...]
các_câu_lệnh_của_trigger
Bảng NHATKYBANHANG lưu trữ thông tin về các lần bán hàng
Trang 39
Giáo trình lập trình cơ sở dữ liệu
Câu lệnh dưới đây định nghĩa trigger trg_nhatkybanhang_insert. Trigger này có
chức năng tự động giảm số lượng hàng hiện có khi một mặt hàng nào đó được bán (tức
là khi câu lệnh INSERT được thực thi trên bảng NHATKYBANHANG).
Với trigger vừa tạo ở trên, nếu dữ liệu trong bảng MATHANG là:
Trong câu lệnh CREATE TRIGGER ở ví dụ trên, sau mệnh đề ON là tên của bảng
mà trigger cần tạo sẽ tác động đến. Mệnh đề tiếp theo chỉ định câu lệnh sẽ kích hoạt
trigger (FOR INSERT). Ngoài INSERT, ta còn có thể chỉ định UPDATE hoặc
DELETE cho mệnh đề này, hoặc có thể kết hợp chúng lại với nhau. Phần thân của
trigger nằm sau từ khoá AS bao gồm các câu lệnh mà trigger sẽ thực thi khi được kích
hoạt.
Chuẩn SQL định nghĩa hai bảng logic INSERTED và DELETED để sử dụng trong
các trigger. Cấu trúc của hai bảng này tương tự như cấu trúc của bảng mà trigger tác
động. Dữ liệu trong hai bảng này tuỳ thuộc vào câu lệnh tác động lên bảng làm kích
hoạt trigger; cụ thể trong các trường hợp sau:
Khi câu lệnh DELETE được thực thi trên bảng, các dòng dữ liệu bị xoá sẽ được
sao chép vào trong bảng DELETED. Bảng INSERTED trong trường hợp này
không có dữ liệu.
Dữ liệu trong bảng INSERTED sẽ là dòng dữ liệu được bổ sung vào bảng gây
nên sự kích hoạt đối với trigger bằng câu lệnh INSERT. Bảng DELETED trong
trường hợp này không có dữ liệu.
Trang 40
Giáo trình lập trình cơ sở dữ liệu
Khi câu lệnh UPDATE được thực thi trên bảng, các dòng dữ liệu cũ chịu sự tác
động của câu lệnh sẽ được sao chép vào bảng DELETED, còn trong bảng
INSERTED sẽ là các dòng sau khi đã được cập nhật.
Thay vì chỉ định một trigger được kích hoạt trên một bảng, ta có thể chỉ định
trigger được kích hoạt và thực hiện những thao tác cụ thể khi việc thay đổi dữ liệu chỉ
liên quan đến một số cột nhất định nào đó của cột. Trong trường hợp này, ta sử dụng
mệnh đề IF UPDATE trong trigger. IF UPDATE không sử dụng được đối với câu lệnh
DELETE.
Ví dụ: Xét lại ví dụ với hai bảng MATHANG và NHATKYBANHANG, trigger dưới
đây được kích hoạt khi ta tiến hành cập nhật cột SOLUONG cho một bản ghi của bảng
NHATKYBANHANG (lưu ý là chỉ cập nhật đúng một dòng)
UPDATE nhatkybanhang
SET soluong=soluong+20
WHERE stt=1
sẽ kích hoạt trigger ứng với mệnh đề IF UPDATE (soluong) và câu lệnh UPDATE
trong trigger sẽ được thực thi.
Trang 41
Giáo trình lập trình cơ sở dữ liệu
Một giao dịch đòi hỏi phải có được bồn tính chất sau đây:
Tính nguyên tử (Atomicity): Mọi thay đổi về mặt dữ liệu hoặc phải được thực
hiện trọn vẹn khi giao dịch thực hiện thành công hoặc không có bất kỳ sự thay
đổi nào về dữ liệu xảy ra nếu giao dịch không thực hiện được trọn vẹn. Nói
cách khác, tác dụng của các câu lệnh trong một giao dịch phải như là một câu
lệnh đơn.
Tính nhất quán (Consistency): Tính nhất quan đòi hỏi sau khi giao dịch kết
thúc, cho dù là thành công hay bị lỗi, tất cả dữ liệu phải ở trạng thái nhất quán
(tức là sự toàn vẹn dữ liệu phải luôn được bảo toàn).
Tính độc lập (Isolation): Tính độc lập của giao dịch có nghĩa là tác dụng của
mỗi một giao dịch phải giống như khi chỉ mình nó được thực hiện trên chính hệ
thống đó. Nói cách khác, một giao dịch khi được thực thi đồng thời với những
giao dịch khác trên cùng hệ thống không chịu bất kỳ sự ảnh hưởng nào của các
giao dịch đó.
Tính bền vững (Durability): Sau khi một giao dịch đã thực hiện thành công,
mọi tác dụng mà nó đã tạo ra phải tồn tại bền vững trong cơ sở dữ liệu, cho dù
là hệ thống có bị lỗi đi chăng nữa.
Một giao dịch sẽ kết thúc trong các trường hợp sau:
Câu lệnh COMMIT TRANSACTION (hoặc COMMIT WORK) được thực thi.
Câu lệnh này báo hiệu sự kết thúc thành công của một giao dịch. Sau câu lệnh
này, một giao dịch mới sẽ được bắt đầu.
Khi câu lệnh ROLLBACK TRANSACTION (hoặc ROLLBACK WORK) được
thực thi để huỷ bỏ một giao dịch và đưa cơ sở dữ liệu về trạng thái như trước
khi giao dịch bắt đầu. Một giao dịch mới sẽ bắt đầu sau khi câu lệnh
ROLLBACK được thực thi.
Trang 42
Giáo trình lập trình cơ sở dữ liệu
Một giao dịch cũng sẽ kết thúc nếu trong quá trình thực hiện gặp lỗi (chẳng hạn
hệ thống gặp lỗi, kết nối mạng bị “đứt”,...). Trong trường hợp này, hệ thống sẽ
tự động phục hồi lại trạng thái cơ sở dữ liệu như trước khi giao dịch bắt đầu
(tương tự như khi câu lệnh ROLLBACK được thực thi để huỷ bỏ một giao
dịch). Tuy nhiên, trong trường hợp này sẽ không có giao dịch mới được bắt
đầu.
Ví dụ: Giao dịch dưới đây kết thúc do lệnh ROLLBACK TRANSACTION và mọi
thay đổi vể mặt dữ liệu mà giao dịch đã thực hiện (UPDATE) đều không có tác dụng.
còn giao dịch dưới đây kết thúc bởi lệnh COMMIT và thực hiện thành công việc cập
nhật dữ liệu trên các bảng Customers và Products.
Trang 43
Giáo trình lập trình cơ sở dữ liệu
Câu lệnh:
SAVE TRANSACTION tên_điểm_dánh_dấu
được sử dụng để đánh dấu một vị trí trong giao dịch. Khi câu lệnh này được thực thi,
trạng thái của cơ sở dữ liệu tại thời điểm đó sẽ được ghi lại trong nhật ký giao dịch.
Trong quá trình thực thi giao dịch có thể quay trở lại một điểm đánh dấu bằng cách sử
dụng câu lệnh:
Trong trường hợp này, những thay đổi về mặt dữ liệu mà giao dịch đã thực hiện từ
điểm đánh dấu đến trước khi câu lệnh ROLLBACK được triệu gọi sẽ bị huỷ bỏ. Giao
dịch sẽ được tiếp tục với trạng thái cơ sở dữ liệu có được tại điểm đánh dấu . Hình
1.30 mô tả cho ta thấy hoạt động của một giao dịch có sử dụng các điểm đánh dấu.
Sau khi câu lệnh ROLLBACK TRANSACTION được sử dụng để quay lui lại một
điểm đánh dấu trong giao dịch, giao dịch vẫn được tiếp tục với các câu lệnh sau đó.
Nhưng nếu câu lệnh này được sử dụng để quay lui lại đầu giao dịch (tức là huỷ bỏ giao
dịch), giao dịch sẽ kết thúc và do đó câu lệnh COMMIT TRANSACTION trong
trường hợp này sẽ gặp lỗi.
Ví dụ: Câu lệnh COMMIT TRANSACTION trong giao dịch dưới đây kết thúc thành
công một giao dịch
Trang 44
Giáo trình lập trình cơ sở dữ liệu
Ví dụ dưới đây minh hoạ cho ta trường hợp các giao dịch lồng nhau.
Ta định nghĩa bảng T như sau:
CREATE TABLE T
(
A INT PRIMARY KEY,
B INT
)
Trang 45
Giáo trình lập trình cơ sở dữ liệu
Lời gọi đến thủ tuch sp_TransEx được thực hiện trong một giao dịch khác như sau:
BEGIN TRANSACTION T3
EXECUTE sp_tranex 10,20
ROLLBACK TRANSACTION T3
Trong giao dịch trên, câu lệnh ROLLBACK TRANSACTION T3 huỷ bỏ giao dịch
và do đó tác dụng của lời gọi thủ tục trong giao dịch không còn tác dụng, tức là không
có dòng dữ liệu nào mới được bổ sung vào bảng T (cho dù giao dịch T1 trong thủ tục
sp_tranex đã thực hiện thành công với lệnh COMMIT TRANSACTION T1).
Ta xét tiếp một trường hợp của một giao dịch khác trong đó có lời gọi đến thủ tục
sp_tranex như sau:
BEGIN TRANSACTION
EXECUTE sp_tranex 20,40
SAVE TRANSACTION a
EXECUTE sp_tranex 30,60
ROLLBACK TRANSACTION a
EXECUTE sp_tranex 40,80
COMMIT TRANSACTION
sau khi giao dịch trên thực hiện xong, dữ liệu trong bảng T sẽ là:
A B
20 40
21 41
40 80
41 81
Như vậy, tác dụng của lời gọi thủ tục sp_tranex 30, 60 trong giao dịch đã bị huỷ bỏ
bởi câu lệnh ROLLBACK TRANSACTION trong giao dịch.
Như đã thấy trong ví dụ trên, khi các giao dịch SQL được lồng vào nhau, giao dịch
ngoài cùng nhất là giao dịch có vai trò quyết định. Nếu giao dịch ngoài cùng nhất được
uỷ thác (commit) thì các giao dịch được lồng bên trong cũng đồng thời uỷ thác; Và
nếu giao dịch ngoài cùng nhất thực hiện lệnh ROLLBACK thì những giao dịch lồng
bên trong cũng chịu tác động của câu lệnh này (cho dù những giao dịch lồng bên trong
đã thực hiện lệnh COMMIT TRANSACTION).
Trong phần tiếp theo, bạn sẽ tìm hiểu thêm về thuật ngữ cơ sở dữ liệu quan hệ và
khảo sát một số bảng trong cơ sở dữ liệu Northwind.
Trang 46
Giáo trình lập trình cơ sở dữ liệu
Đây là cơ sở dữ liệu chứa thông tin bán hàng của công ty Northwind thường được
cài đặt sẵn trong các phiên bản của Microsoft SQL Server. Hầu hết các ví dụ được sử
dụng trong giáo trình này đều minh họa dựa trên cấu trúc của 4 bảng Customers,
Orders, Order Details và Products. Vì thế, trước khi đi vào chi tiết, chúng ta cần khảo
sát sơ lược về các bảng này.
Trang 47
Giáo trình lập trình cơ sở dữ liệu
Trang 48
Giáo trình lập trình cơ sở dữ liệu
Trang 49
Giáo trình lập trình cơ sở dữ liệu
Trang 50
Giáo trình lập trình cơ sở dữ liệu
Trang 51
Giáo trình lập trình cơ sở dữ liệu
Ở khung bên trái của Enterprise Manager, có một cây cho thấy các thành phần
được cài đặt trong SQL Server. Nội dung ở khung bên phải hiển thị các thông tin khác
nhau tùy thuộc vào mục mà bạn chọn ở khung bên trái. Chẳng hạn, nếu chọn thư mục
Databases và cơ sở dữ liệu Northwind, ở khung bên phải sẽ hiển thị các biểu tượng
cho phép hiệu chỉnh từng mục được lưu trong cơ sở dữ liệu đó.
Mỗi bộ cài đặt SQL Server đều chứa 7 thư mục sau (nằm trong khung bên trái):
Databases: Chứa các công cụ cho phép bạn truy xuất đến các cơ sở dữ liệu
được quản lý bởi SQL Server.
Data Transformation Services: Cung cấp truy xuất đến các công cụ cho phép
bạn chuyển tải dữ liệu từ một cơ sở dữ liệu này sang cơ sở dữ liệu khác hoặc có
thể thay đổi dữ liệu từ hệ quản trị này sang hệ quản trị khác. Chẳng hạn, bạn có
thể chuyển dữ liệu từ cơ sở dữ liệu SQL Server sang một cơ sở dữ liệu Oracle
và ngược lại.
Management: chứa các công cụ cho phép bạn sao lưu các cơ sở dữ liệu, điều
khiển các hoạt động trên cơ sở dữ liệu hiện hành và nhiều tác vụ khác.
Replication: cung cấp các công cụ truy xuất cho phép bạn sao chép thông tin từ
một cơ sở dữ liệu này sang cơ sở dữ liệu khác theo thời gian thực. Quá trình
này được gọi là sự tái tạo (replication). Chẳng hạn như chuyển dữ liệu từ một
cơ sở dữ liệu đang hoạt động tại một chi nhánh của công ty về một cơ sở dữ liệu
tại tổng công ty.
Sercurity: Chứa các công cụ cho phép bạn quản lý việc đăng nhập và các quyền
được xây dựng sẵn. Bạn cũng có thể quản lý các server liên kết (linked servers)
và các server được kết nối từ xa (remote servers). Các server liên kết là các cơ
sở dữ liệu mà bạn có thể truy xuất thông qua mạng. Những cơ sở dữ liệu này
không nhất thiết phải là các cơ sở dữ liệu SQL Server mà có thể là cơ sở dữ liệu
Oracle. Chỉ có một giới hạn là chúng phải là cơ sở dữ liệu được quản lý bởi một
trình cung cấp dịch vụ OLE DB (Object Linking and Embedding for Databases
provider). Server kết nối từ xa là các cơ sở dữ liệu SQL Server mà bạn có thể
truy xuất thông qua mạng và chạy các thủ tục được lưu trữ trên đó.
Support Services: Cung cấp truy xuất đến các công cụ cho phép bạn quản lý bộ
điều phối giao dịch phân tán (Distributed Transaction Coordinator), tìm kiếm
văn bản toàn diện (Full-Text Search), và các dịch vụ thư điện tử SQL (SQL
Mail Services). Dịch vụ điều phối giao dịch phân tán cho phép bạn quản lý các
giao dịch sử dụng trên nhiều cơ sở dữ liệu. Dịch vụ tìm kiếm cho phép bạn thực
hiện tìm kiếm các cụm từ trong một văn bản lớn. Dịch vụ SQL Mail cho phép
bạn gửi thư điện tử từ chính SQL Server.
Metadata Services: Chứa các công cụ cho phép bạn quản lý thông tin được lưu
trữ trong các kho cục bộ. Thông tin này chứa chi tiết về các cơ sở dữ liệu, người
dùng, các bảng, các cột, khung nhìn (views), các thủ tục (stored procedure),…
Những thông tin này chủ yếu được sử dụng bởi các ứng dụng kho dữ liệu (data-
warehousing).
Trang 52
Giáo trình lập trình cơ sở dữ liệu
Thư mục Databases chứa các cơ sở dữ liệu được quản lý bởi SQL Server. Chẳng
hạn, trong hình 1.13, SQL Server quản lý 7 cơ sở dữ liệu có tên: Essay, master, model,
msdb, Northwind, pubs và tempdb. Khi nhấp vào dấu cộng (+) bên cạnh tên một cơ sở
dữ liệu, bạn sẽ thấy các thành phần sau:
Trang 53
Giáo trình lập trình cơ sở dữ liệu
dung của view được viết dưới dạng lệnh SQL (sẽ được trình bày trong phần
sau).
Bạn có thể tạo các bảng mới, xem thuộc tính của một bảng hay truy vấn các
dòng dữ liệu từ một bảng. Để xem các thuộc tính của bảng, chọn bảng đó từ
danh sách trong khung bên phải, nhắp chuột phải và chọn Properties hoặc nhấp
đôi chuột lên tên bảng. Hình 1.16 cho thấy các thuộc tính của bảng Customers
trong cơ sở dữ liệu Northwind.
Trang 54
Giáo trình lập trình cơ sở dữ liệu
Hình 1.17. Các thuộc tính của khung nhìn Alphabetical list of products
Trang 55
Giáo trình lập trình cơ sở dữ liệu
Trang 56
Giáo trình lập trình cơ sở dữ liệu
Users: Mỗi khi truy xuất đến cơ sở dữ liệu, bạn phải kết nối tới cơ sở dữ liệu đó
thông qua một tài khoản người dùng. Mỗi cơ sở dữ liệu SQL Server chứa hai
người dùng mặc định có tên là dbo và guest. Tài khoản dbo quản lý các cơ sở
dữ liệu và có quyền thực hiện mọi việc trên cơ sở dữ liệu đó như tạo bảng, hiệu
chỉnh các bảng,… Tài khoản guest bị giới hạn hơn, chỉ cho phép truy xuất đến
nội dung của các bảng, không có khả năng tạo và thay đổi cấu trúc bảng như
dbo. Hình 1.19 cho thấy các thuộc tính của tài khoản dbo. Bạn có thể thấy tài
khoản này đóng hai vai trò: public và db_owner. Để xem tất cả các quyền của
tài khoản dbo, nhấp chuột vào nút Permission.
Roles: Vai trò (role) là tên của một tập các quyền mà bạn gán cho một tài khoản
người dùng nào đó. Điều này đặc biệt hữu ích khi bạn muốn gán cùng một tập
quyền cho nhiều người dùng. Theo cách này, nếu cần thay đổi tập quyền, bạn
chỉ cần thay đổi các quyền được gán trong role thay vì các quyền được gán cho
mỗi người dùng. Chẳng hạn, trong hình 1.19, tài khoản dbo có hai role: public
và db_owner. Hình 1.20 cho thấy các thuộc tính của public role. Kể cả tài
khoản guest cũng có vai trò này. Nếu không có public role nào được dùng, tập
các quyền phải được thêm vào các tài khoản dbo và guest bằng tay. Bạn cũng
có thể xem quyền được gán cho một role bằng cách nhấp chuột vào nút
Permissions. Hình 1.21 chỉ ra các thuộc tính được gán cho public role.
Trang 57
Giáo trình lập trình cơ sở dữ liệu
Trang 58
Giáo trình lập trình cơ sở dữ liệu
Defaults: Một giá trị mặc định là một giá trị khởi đầu được đặt khi bạn thêm
một mẫu tin mới vào một bảng. Các giá trị mặc định được cung cấp để tương
thích với các phiên bản cũ của SQL Server và đã được thay thế bởi thuộc tính
Default Value trong mỗi cột.
User-defined Data Types: Kiểu dữ liệu do người dùng định nghĩa cho phép bạn
tạo riêng cho mình một kiểu dữ liệu mới dựa trên các kiểu cơ sở của SQL
Server. Chẳng hạn, bạn muốn lưu trữ mã số bưu điện (ZIP Code) của USA
trong vài bảng của cơ sở dữ liệu, bạn có thể tạo ra một kiểu dữ liệu lưu trữ một
chuỗi gồm 5 ký tự. Sau đó, nếu muốn tăng chiều dài của chuỗi lên 8 ký tự, chỉ
cần thay đổi kiểu dữ liệu của bạn và những thay đổi này sẽ tự động ảnh hưởng
lên tất cả các bảng mà bạn sử dụng kiểu dữ liệu này.
User-defined Functions: Phần này cho phép bạn tạo riêng cho mình các hàm
xử lý. Chẳng hạn, bạn muốn kiểm tra một số có phải là số nguyên tố không, bạn
có thể tạo ra một hàm để xử lý việc này.
Full-Text Catalogs: cho phép bạn tạo ra một chỉ mục đầy đủ cho phép bạn thực
hiện tìm kiếm cụm từ trong một văn bản lớn.
1.6.1.1. Xây dựng các lệnh truy vấn dùng Enterprise Manager
Ta có thể tạo ra các lệnh truy vấn để rút trích dữ liệu từ các dòng của bảng bằng
cách sử dụng Enterprise Manager. Trong phần này, chúng ta sẽ tìm hiểu cách tạo và
thực thi một lệnh truy vấn để xem các đơn đặt hàng của khách hàng có mã số
(CustomerID) là ALFKI kèm theo chi tiết đơn hàng và các sản phẩm trong đơn hàng
có mã (OrderID) là 10463. Cụ thể, ta sẽ chọn các cột sau:
- CustomerID và CompanyName từ bảng Customers
- OrderID và OrderDate từ bảng Orders
- ProductID và Quantity từ bảng Order Details
Để xây dựng lệnh truy vấn, chọn bảng Customers trong mục Tables của cơ sở dữ
liệu Northwind trong Enterprise Manager, nhắp phải chuột và chọn Open Table
Query. Xuất hiện cửa sổ Query Builder như hình 1.26.
Trang 59
Giáo trình lập trình cơ sở dữ liệu
Hình 1.26. Trình xây dựng lệnh truy vấn (Query Builder)
Khung nằm trên cùng được gọi là khung lược đồ (Diagram Pane) và nó hiển thị
các bảng được dùng trong truy vấn. Trong hình trên, khung này chứa bảng Customers
- là bảng mà bạn đã chọn trước đó. Khung bên dưới được gọi là Grid Pane cho thấy
chi tiết các cột và các hàng được truy vấn dữ liệu từ các bảng. Ban đầu, tất cả các hàng
đều được trích rút từ bảng Customers, điều này được thể hiện bởi dấu sao (*) trong
Grid Pane. Bên dưới Grid Pane là khung chứa lệnh truy vấn (SQL Pane). SQL
(Structured Query Language) là một ngôn ngữ truy vấn dữ liệu dựa trên văn bản.
Dưới SQL Pane là khung hiển thị kết quả truy vấn (Results Pane). Ban đầu, khung
này trống rỗng vì chưa có truy vấn nào được thực hiện. Để xây dựng một truy vấn,
thực hiện các bước sau đây:
Trang 60
Giáo trình lập trình cơ sở dữ liệu
Hình 1.27. Kết quả xây dựng và thực thi một lệnh truy vấn
- Xóa dấu * trong khung Grid Pane bằng cách nhắp phải chuột lên ô bên trái
dòng chứa dấu * và chọn Delete. Việc này cũng xóa tất cả các cột khỏi lệnh
truy vấn.
- Nhắp phải chuột lên Diagram Pane và chọn Add Table. Thêm các bảng Orders
và Order Details vào để bạn có thể tạo truy vấn trên các bảng này. Bạn cũng có
thể nhấp nút Add Table trên thanh công cụ để thêm bảng. Chú ý rằng, sau khi
thêm bảng, chúng xuất hiện trong Diagram Pane cùng với các đường liên kết
(mối quan hệ) giữa các bảng cha con thông qua khóa ngoại. Chẳng hạn, bảng
Customers nối với bảng Orders qua cột CustomersID. Tương tự, bảng Orders
và Order Details nối với nhau qua cột OrderID.
- Đánh dấu chọn vào các ô nhỏ bên trái cột CustomerID và CompanyName của
bảng Customers trong Diagram Pane.
- Tương tự, chọn các cột OrderID và OrderDate trong bảng Orders.
- Chọn các cột ProductID và Quantity trong bảng Order Details.
- Trong Grid Pane, đặt điều kiện (criteria) cho cột CustomerID là = ‘ALFKI’.
Điều này có nghĩa là chỉ truy vấn các dòng dữ liệu trong bảng Customers có giá
trị cột CustomerID là “ALFKI”.
Trang 61
Giáo trình lập trình cơ sở dữ liệu
- Trong Grid Pane, đặt điều kiện cho cột OrderID là = 10463. Việc này làm cho
lệnh truy vấn chỉ trả về các dòng trong bảng Orders có giá trị trên cột OrderID
là 10463.
- Thực thi câu lệnh truy vấn bằng cách nhấp vào nút Run trên thanh công cụ.
Hình 1.27 cho thấy kết quả xây dựng và thực thi câu truy vấn.
Trang 62
Giáo trình lập trình cơ sở dữ liệu
thước (chiều dài) của các cột có kiểu như nchar, nvarchar vì những kiểu này được
thiết kế để lưu trữ dữ liệu có kích thước thay đổi.
Nhấn nút Save trên thanh công cụ để lưu lại bảng vừa tạo. Trong hộp thoại Choose
Name, nhập tên bảng là Persons rồi nhấp OK để lưu lại như hình 1.29 sau đây.
Trang 63
Giáo trình lập trình cơ sở dữ liệu
Trường IsRowGuid chỉ định một cột nào đó phải chứa các giá trị định danh duy
nhất (giá trị phân biệt) hay còn gọi là định danh duy nhất toàn cục (GUID – Globally
Unique Identifier). SQL Server không tự động gán giá trị cho cột có kiểu GUID. Nếu
muốn SQL Server tạo ra một GUID, ta dùng hàm NEWID() được cung cấp sẵn trong
SQL Server. Hàm NEWID() luôn trả về một giá trị khác với những giá trị đã sinh ở lần
trước. Sau đó, bạn có thể dùng kết quả trả về của hàm này làm giá trị mặc định
(Default Value) cho cột có kiểu uniqueidentifier. Trong trường hợp này, bạn đặt giá
trị cho trường Default Value là [NEWID()].
Trường Formula cho phép bạn gán công thức để tính toán giá trị cho một cột.
Trường Collation định ra các quy tắc được dùng để sắp xếp và so sánh các ký tự.
Trường này được dùng nhiều khi làm việc với nhiều ngôn ngữ.
Hình 1.30. Gán quyền cho public role trên bảng Persons.
Trang 64
Giáo trình lập trình cơ sở dữ liệu
Trang 65
Giáo trình lập trình cơ sở dữ liệu
cũng ngăn chặn một hàng trong bảng chứa khóa chính bị xóa bỏ trong khi vẫn
còn có một hàng trong bảng chứa khóa ngoại tham chiếu đến giá trị trên cột
khóa chính.
- Cascade Update Related Fields: Yêu cầu SQL Server tự động cập nhật giá trị
khóa ngoại của quan hệ khi giá trị khóa chính bị thay đổi.
- Cascade Delete Related Records: Yêu cầu SQL Server tự động xóa bỏ các hàng
trong bảng chứa khóa ngoại khi dòng được tham chiếu trong bảng chứa khóa
chính bị xóa. Nghĩa là, khi một dòng trong bảng cha bị xóa thì các dòng trong
bảng con có liên quan (có quan hệ hay tham chiếu đến) cũng bị xóa theo.
Trang 66
Giáo trình lập trình cơ sở dữ liệu
Bạn không cần phải thay đổi giá trị trong các mục khác khi tạo chỉ mục. Tuy nhiên,
vẫn cần phải biết ý nghĩa của các trường này.
- Index Filegroup: là nhóm tập tin mà bạn muốn lưu trữ chỉ mục. Một filegroup
được tạo ra từ một hay nhiều tập tin vật lý trên đĩa cứng máy tính. SQL Server
sử dụng các filegroup để lưu trữ thông tin thực sự tạo nên một cơ sở dữ liệu.
- Create UNIQUE: tùy chọn này cho phép bạn tạo một ràng buộc về tính duy
nhất của dữ liệu hay tạo chỉ mục cho bảng dữ liệu được chọn. Cần phải chỉ rõ
mục đích tạo ràng buộc duy nhất hay chỉ mục bằng cách chọn nút Constraint
hay Index
- Ignore duplicate key: nếu bạn tạo một chỉ mục duy nhất, có thể chọn thêm mục
này để loại bỏ các giá trị trùng nhau.
- Fill factor: Nên để giá trị mặc định cho mục này, trừ khi bạn là một người dùng
SQL Server chuyên nghiệp. Đơn vị lưu trữ nhỏ nhất trong một cơ sở dữ liệu
SQL Server là một trang (page), có thể lưu giữ 8096 bytes dữ liệu. Dữ liệu cho
các bảng và chỉ mục được lưu trong nhiều trang. Bạn có thể định ra dung lượng
trang chỉ mục (khi nào thì đầy trang) bằng cách thiết lập fill factor. Chẳng hạn,
nếu đặt giá trị này là 60% thì trang sẽ chứa 60% dữ liệu và 40% để trống.
Lượng không gian trống trong một trang chỉ mục là rất quan trọng vì khi một
trang chỉ mục đầy, SQL Server phải tách trang để tạo không gian cho dữ liệu
chỉ mục mới. Do đó, bằng cách giảm fill factor, bạn có thể tăng hiệu suất của cơ
sở dữ liệu vì SQL Server không phải tách trang thường xuyên. Tuy nhiên, việc
giảm fill factor cũng làm cho chỉ mục chiếm nhiều không gian đĩa vì sẽ có
nhiều khoảng trống trên mỗi trang. Tốt nhất, bạn nên để trống mục này, cơ sở
dữ liệu sẽ sử dụng giá trị mặc định.
- Pad Index: không nên sử dụng tùy chọn này nếu bạn không phải là người dùng
SQL Server chuyên nghiệp. Nếu giá trị fill factor khác 0 và bạn đang tạo một
chỉ mục duy nhất qua tùy chọn Create UNIQUE thì cần phải chọn Pad Index.
Việc này cho SQL Server biết nó cần dùng cùng một dung lượng bộ nhớ để cho
phép tạo các nút lá trên cây nhị phân chỉ mục.
- Create as CLUSTERED: sử dụng tùy chọn này cho biết chỉ mục bạn tạo được
gom nhóm với nhau. Một chỉ mục được gom nhóm là một chỉ mục chứa các
dòng thực sự của bảng thay vì chứa con trỏ đến dòng đó. Các chỉ mục gom
nhóm cho phép truy xuất nhanh hơn nhưng yêu cầu nhiều thơi gian hơn khi
thêm các dòng mới.
- Do not automatically recompute statistics: Thông thường, bạn không nên dùng
tùy chọn này vì nó có thể làm giảm hiệu suất. Khi tạo chỉ mục, SQL Server tự
động lưu trữ các thông tin thống kê về sự phân phối dữ liệu trong các cột được
tạo chỉ mục. SQL Server sử dụng các thông tin thống kê này để ước lượng chi
phí sử dụng chỉ mục cho mỗi truy vấn. Sử dụng tùy chọn này nếu muốn cho
SQL Server biết không cần phải cập nhật các thông tin mới mà chỉ dùng những
thông kê đã tạo trước đó.
Trang 67
Giáo trình lập trình cơ sở dữ liệu
Trang 68
Giáo trình lập trình cơ sở dữ liệu
- Enforce constraint for replication: sử dụng tùy chọn này để áp dụng ràng buộc
khi bảng được sao chép sang một cơ sở dữ liệu khác bằng cách tái tạo.
- Enforce constraint for INSERTs and UPDATEs: sử dụng tùy chọn này để áp
dụng ràng buộc khi dữ liệu được thêm vào bảng hay bị sửa đổi.
Trang 69
Giáo trình lập trình cơ sở dữ liệu
Trong hình 1.35, các lệnh SQL nằm ở phần trên của cửa sổ Query còn kết quả truy
vấn được hiển thị ngay bên dưới. Cơ sở dữ liệu cần truy xuất được chỉ ra bởi mệnh đề
USE và các dòng trong bảng được truy xuất thông qua mệnh đề SELECT. Mệnh đề
SELECT chỉ ra rằng bạn muốn truy xuất dữ liệu trên hai cột CustomerID và
CompanyName của bảng Customers.
SELECT và FROM là các từ khóa của SQL. SQL không phân biệt chữ hoa – chữ
thường. Nên kết thúc mỗi lệnh SQL bởi một dấu chấm phẩy “;”.
Bạn có thể thực thi câu lệnh SQL đã tạo trong cửa sổ Query theo một trong năm cách:
Trang 70
Giáo trình lập trình cơ sở dữ liệu
Trong chương này, bạn đã được học cách sử dụng SQL để truy cập đến một cơ sở
dữ liệu. SQL là ngôn ngữ chuẩn cho việc truy xuất các cơ sở dữ liệu quan hệ. Với
SQL, bạn chỉ cho cơ sở dữ liệu biết cần lấy dữ liệu nào và phần mềm quản trị sẽ tự tìm
ra chính xác những mẫu dữ liệu đó. Nếu sử dụng SQL Server, bạn có thể tạo và thực
thi các truy vấn SQL bằng Query Builder của Enterprise Manager hoặc Query
Analyzer.
Có hai nhóm lệnh SQL chính: nhóm lệnh xử lý dữ liệu (DML) và nhóm lệnh định
nghĩa cấu trúc của dữ liệu (DDL). Các lệnh DML cho phép bạn lấy, thêm, cập nhật
hay xóa các dòng được lưu trong cơ sở dữ liệu. Trong khi đó, các lệnh DDL cho phép
tạo ra cấu trúc của cơ sở dữ liệu như các bảng, chỉ mục, thủ tục và hàm,…
Để lấy các dòng từ một hay nhiều bảng, bạn dùng lệnh SQL SELECT. Lệnh
INSERT được dùng để bổ sung dữ liệu cho một bảng. Lệnh UPDATE được dùng để
cập nhật dữ liệu. Và cuối cùng, lệnh DELETE dùng để xóa các dòng khỏi một bảng.
Trang 71
Giáo trình lập trình cơ sở dữ liệu
Chương này cũng hướng dẫn cách tạo các thủ tục lưu trữ, trigger và hàm do người
dùng định nghĩa. Thủ tục (Stored Procedure) khác hàm do người dùng định nghĩa ở
chổ nó có thể trả về một mảng các dữ liệu có nhiều kiểu khác nhau. Thông thường, bạn
nên tạo thủ tục khi muốn thực hiện một công việc phức tạp hoặc để người dùng khác
sử dụng lại các đoạn mã thay vì phải viết lại để thực hiện cùng một việc.
Phần cuối chương trình bày cách sử dụng chương trình Enterprise Manager và
Query Analyzer được cài đặt kèm trong phần mềm SQL Server. Bạn cũng đã được
khảo sát cấu trúc của cơ sở dữ liệu Northwind. Đây là cơ sở dữ liệu sẽ được dùng làm
ví dụ trong suốt giáo trình này.
Trang 72
Giáo trình lập trình cơ sở dữ liệu
2. CHƯƠNG 2
TỔNG QUAN VỀ ADO.NET
ADO.NET cho phép bạn tương tác trực tiếp với một cơ sở dữ liệu thông qua đối
tượng thuộc các lớp kết nối (hay lớp được quản lý bởi trình cung cấp - managed
provider). Các đối tượng này cho phép kết nối tới cơ sở dữ liệu và thực thi các lệnh
SQL khi luồng kết nối được mở.
ADO.NET cũng cho phép làm việc ở dạng không kết nối, tức là thông tin từ cơ sở
dữ liệu được lưu trực tiếp trong bộ nhớ máy tính mà chương trình đang chạy. Những
thông tin này được lưu trữ trong một đối tượng thuộc lớp DataSet. Một khi đã có
những thông tin này trong bộ nhớ, ta có thể đọc, xử lý chúng một cách trực tiếp.
Chẳng hạn, bạn có thể hiển thị tên các cột, thêm một dòng mới, cập nhật dữ liệu hay
xóa một dòng nào đó. Sau một khoảng thời gian, bạn cần kết nối lại cơ sở dữ liệu để
đồng bộ dữ liệu đã thay đổi trên máy cục bộ với cơ sở dữ liệu. Mô hình không kết nối
này cho phép bạn viết các ứng dụng chạy trên Internet cũng như cho các thiết bị không
phải lúc nào cũng được kết nối tới cơ sở dữ liệu như PDAs (Palm hay Pocket PC).
Chương này trình bày mô tả sơ lược về các lớp của ADO.NET và hướng dẫn cách
viết một chương trình hoàn chỉnh có kết nối đến cơ sở dữ liệu, lưu trữ dữ liệu tại máy
cục bộ, đóng kết nối và đọc nội dung các dòng trong khi không còn kết nối tới cơ sở
dữ liệu. Khả năng lấy một bản sao các dòng từ cơ sở dữ liệu và lưu trữ tại máy cục bộ
là một trong những điểm mạnh của ADO.NET. Phần cuối chương hướng dẫn cách cập
nhật dữ liệu và đồng bộ hóa các thay đổi với cơ sở dữ liệu.
Trang 73
Giáo trình lập trình cơ sở dữ liệu
Server, Access hay Oracle… Ngoài ra, các lớp này biểu diễn thông tin lấy từ cơ sở dữ
liệu ở định dạng XML.
Hiện tại, có 3 nhóm lớp kết nối được thiết kế để làm việc với 3 chuẩn cơ sở dữ liệu
khác nhau:
- SQL Server Managed Provider Classes: được dùng để kết nối tới một cơ sở dữ
liệu SQL Server.
- OLE DB Managed Provider Classes: được dùng để kết nối tới bất kỳ cơ sở dữ
liệu nào hỗ trợ chuẩn OLE DB (Object Linking and Embedding for Databases)
như Access hay Oracle.
- ODBC Managed Provider Classes: được dùng để kết nối tới các cơ sở dữ liệu
có hỗ trợ chuẩn ODBC (Open Database Connectivity). Tất cả các cơ sở dữ liệu
phổ biến đều hỗ trợ ODBC, tuy nhiên ODBC thường chậm hơn so với hai nhóm
Trang 74
Giáo trình lập trình cơ sở dữ liệu
trước khi làm việc với .Net. Chỉ nên dùng nhóm lớp này khi không còn cách
nào khác để thay thế nhóm OLE DB.
Tất cả các nhóm lớp kết nối này đều được xây dựng cùng một tập chức năng, nghĩa
là có cùng các hàm xử lý. Tên của các chuẩn này được đặt trước tên của các lớp kết
nối để cho biết nhóm lớp nào đang được dùng. Chẳng hạn, nếu tên lớp kết nối bắt đầu
bằng Sql thì cơ sở dữ liệu đang dùng được quản lý bởi SQL Server. Tương tự, Oledb
được dùng cho cơ sở dữ liệu hỗ trợ OLE DB. Cuối cùng, Odbc được dùng cho cơ sở
dữ liệu hỗ trợ ODBC. Ví dụ: SqlConnection, OledbConnection, OdbcConnection,
SqlCommand,… Để đơn giản, ta chỉ gọi tên chung của chúng. Chẳng hạn, lớp
Connection, lớp Command…
Trang 75
Giáo trình lập trình cơ sở dữ liệu
Trang 76
Giáo trình lập trình cơ sở dữ liệu
báo trong namespace System.Data.OleDb. Các lớp kết nối cho cơ sở dữ liệu hỗ trợ
ODBC được khai báo trong namespace System.Data.Odbc.
Trang 77
Giáo trình lập trình cơ sở dữ liệu
Các dòng này được truy xuất thông qua thuộc tính Rows của lớp DataTable. Thuộc
tính này có kiểu DataRowCollection.
Trang 78
Giáo trình lập trình cơ sở dữ liệu
xuất thông qua thuộc tính Constraints của lớp DataTable. Thuộc tính này có kiểu là
ConstraintCollection.
2.3.5.1. Lớp UniqueConstraint
Đối tượng thuộc lớp UniqueConstraint được dùng để biểu diễn một ràng buộc cơ
sở dữ liệu áp dụng cho các giá trị được lưu trong một cột sao cho giá trị đó là duy nhất.
Nghĩa là trong cùng một cột, không có hai giá trị trùng nhau. Lớp UniqueConstraint kế
thừa từ lớp Constraint. Một DataTable có thể chứa nhiều đối tượng UniqueConstraint
qua thuộc tính Constraints. Thuộc tính này có kiểu ConstraintCollection.
2.3.5.2. Lớp ForeignKeyConstraint
Đối tượng có kiểu ForeignKeyConstraint được dùng chỉ ra các thao tác được thực
hiện khi các giá trị trong cột khóa chính của bảng cha được cập nhật hay bị xóa.
Lớp ForeignKeyConstraint được dẫn xuất từ lớp Constraint. Bạn có thể xóa các
dòng trong bảng con hoặc gán giá trị cho cột khóa ngoại là null hoặc giá trị mặc định.
Một DataTable có thể chứa nhiều đối tượng ForeignKeyConstraint qua thuộc tính
Constraints. Thuộc tính này có kiểu là ConstraintCollection.
2.4.1. Các bước thực hiện một truy vấn trong ADO.Net
1. Khai báo một biến chuỗi lưu trữ thông tin chi tiết kết nối tới cơ sở dữ liệu.
2. Tạo một đối tượng SqlConnection để kết nối tới cơ sở dữ liệu bằng cách truyền
chuỗi kết nối (khai báo ở bước 1) qua tham số của phương thức tạo lập.
3. Khai báo biến chuỗi chứa câu lệnh SELECT để lấy dữ liệu từ bảng.
4. Tạo một đối tượng SqlCommand để lưu giữ lệnh SELECT.
5. Đặt giá trị thuộc tính CommandText của đối tượng SqlCommand bằng biến
chuỗi chứa lệnh SELECT (trong bước 3).
6. Tạo một đối tượng SqlDataAdapter.
7. Đặt giá trị thuộc tính SelectCommand của đối tượng SqlDataAdapter bằng với
đối tượng SqlCommand ở bước 4.
8. Tạo một đối tượng DataSet để lưu kết quả của lệnh truy vấn SELECT.
9. Mở kết nối đến cơ sở dữ liệu bằng cách gọi phương thức Open() của đối tượng
SqlConnection.
10. Gọi phương thức Fill() của đối tượng SqlDataAdapter để lấy các dòng từ bảng
và lưu các dòng đó vào một DataTable của đối tượng DataSet. Việc này được
thực hiện bằng cách truyền đối tượng DataSet đã tạo ở bước 8 vào tham số của
phương thức Fill.
11. Đóng kết nối tới cơ sở dữ liệu bằng cách gọi phương thức Close() của đối tượng
SqlConnection đã khai báo ở bước 1.
12. Lấy đối tượng DataTable từ đối tượng DataSet.
13. Hiển thị dữ liệu trên các cột và các dòng của DataTable bằng cách dùng đối
tượng DataRow để truy xuất lần lượt qua các dòng của bảng.
Trang 80
Giáo trình lập trình cơ sở dữ liệu
Ví dụ sau tạo một biến kiểu chuỗi tên là connectionString và gán cho nó một giá trị
tương ứng với chuỗi kết nối tới cơ sở dữ liệu Northwind trên một máy tính cục bộ cho
phép người dùng có tên sa truy cập với mật khẩu sa.
string connectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
Từ khóa TOP được dùng kết hợp với ORDER BY để lấy về N dòng đầu tiên trong
bảng Customers.
Trang 81
Giáo trình lập trình cơ sở dữ liệu
mySqlDataAdapter.Fill(myDataSet, "Customers");
Trang 82
Giáo trình lập trình cơ sở dữ liệu
Có thể lấy một DataTable từ DataSet thông qua thuộc tính Tables. Thuộc tính này
trả về một DataTableCollection. Để lấy một DataTable nào đó từ DataSet, ta dùng tên
của DataTable (chẳng hạn “Customers”) đặt trong cặp dấu móc vuông [ ] ngay sau
thuộc tính Tables.
Bạn cũng có thể chỉ ra DataTable muốn lấy bằng cách thay tên bảng bằng chỉ số
của bảng đó trong tập các DataTable. Với trường hợp này, bạn đặt chỉ số của
DataTable trong cặp dấu móc vuông [ ] ngay sau thuộc tính Tables của DataSet. Chẳng
hạn: myDataSet.Tables[0] trả về DataTable đầu tiên.
Trong ví dụ chúng ta đang thực hiện, để lấy bảng Customers trong DataSet, ta tạo
một biến DataTable và gán giá trị là bảng Customers từ DataSet.
Mỗi đối tượng DataRow lưu trữ nhiều đối tượng DataColumn cùng với giá trị nhận
được từ các cột của bảng trong cơ sở dữ liệu. Ta có thể truy xuất đến giá trị các cột này
bằng cách đặt tên cột trong cặp dấu móc vuông ngay sau đối tượng DataRow. Chẳng
hạn, muốn lấy dữ liệu trong cột CustomerID của một dòng, ta viết
myDataRow[“CustomerID”]. Bạn cũng có thể thay tên cột bằng chỉ số của nó trong
danh sách các cột được lưu bởi DataRow.
Trong chương trình đang viết, chúng ta sử dụng một vòng lặp foreach để duyệt qua
các đối tượng DataRow trong DataTable sau đó xuất giá trị các cột ra màn hình.
foreach (DataRow myDataRow in myDataTable.Rows)
{
Console.WriteLine("CustomerID = " + myDataRow["CustomerID"]);
Console.WriteLine("CompanyName = " + myDataRow["CompanyName"]);
Console.WriteLine("ContactName = " + myDataRow["ContactName"]);
Console.WriteLine("Address = " + myDataRow["Address"]);
Console.WriteLine("=========================================");
}
Sau đây là toàn bộ mã nguồn cho việc truy vấn và xuất thông tin các khách hàng từ
bảng Customers trong cơ sở dữ liệu Northwind.
Trang 83
Giáo trình lập trình cơ sở dữ liệu
/*
SelectIntoDataSet.cs illustrates how to perform a SELECT
statement and store the returned rows in a DataSet object
*/
using System;
using System.Data;
using System.Data.SqlClient;
class SelectIntoDataSet
{
string connectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
SqlConnection mySqlConnection =
new SqlConnection(connectionString);
string selectString =
"SELECT TOP 10 CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"ORDER BY CustomerID";
mySqlCommand.CommandText = selectString;
mySqlDataAdapter.SelectCommand = mySqlCommand;
Trang 84
Giáo trình lập trình cơ sở dữ liệu
mySqlConnection.Open();
// step 11: close the database connection using the Close() method
// of the SqlConnection object created in Step 2
mySqlConnection.Close();
// step 12: get the DataTable object from the DataSet object
// step 13: display the columns for each row in the DataTable,
// using a DataRow object to access each row in the DataTable
}
}
Trang 85
Giáo trình lập trình cơ sở dữ liệu
CONTROL DESCRIPTION
Label Dùng để hiển thị một đoạn văn bản. Văn bản này được gán qua
thuộc tính Text.
LinkLabel Giống như Label và có thêm đường liên kết đến một trang web.
Văn bản hiển thị được gán qua thuộc tính Text còn đường liên
kết được đặt qua sự kiện LinkClicked.
Button Là một nút nhấn. Văn bản hiển thị trên nút được gán cho thuộc
tính Text.
TextBox Một khung chứa văn bản cho phép người dùng có thể nhập dữ
liệu khi chương trình đang chạy. Dữ liệu nhập được truy xuất
Trang 86
Giáo trình lập trình cơ sở dữ liệu
CONTROL DESCRIPTION
qua thuộc tính Text.
MainMenu Trình đơn chứa các chức năng mà chương trình có thể thực
hiện.
CheckBox Một hộp kiểm chứa giá trị true hoặc false. Giá trị này được
chứa trong thuộc tính Checked. Nếu hộp kiểm được đánh dấu,
Checked có giá trị là true.
RadioButton Tương tự như CheckBox nhưng nếu có nhiều RadioButton nằm
trong cùng một nhóm (đặt trong cùng 1 GroupBox) thì tại mỗi
thời điểm chỉ có một RadioButton được chọn.
GroupBox Cho phép gom nhóm các điều khiển có liên quan lại với nhau.
Chẳng hạn: dùng GroupBox để nhóm 2 RadioButton thể hiện giới
tính của một người.
PictureBox Điều khiển dùng để hiển thị một hình ảnh. Đường dẫn của hình
ảnh được gán cho thuộc tính ImageLocation. Hình ảnh được gán
cho PictureBox hoặc truy xuất qua thuộc tính Image.
Panel Là một điều khiển dùng làm khung chứa các điều khiển khác như
RadioButton, GroupBox…
DataGrid Là một lưới dùng để chứa và hiển thị dữ liệu lấy được từ một
nguồn dữ liệu (DataSource). Nguồn dữ liệu được gán cho
DataGrid qua thuộc tính DataSource.
ListBox Là một danh sách các lựa chọn. Các lựa chọn này được đưa vào
ListBox thông qua phương thức Add của thuộc tính Items.
CheckedListBox Tương tự như ListBox nhưng bên trái mỗi lựa chọn còn có thêm
một hộp kiểm để cho phép người dùng đánh dấu chọn những mục
quan tâm. Có thể chọn cùng lúc nhiều giá trị bằng cách dùng
phím Shift hoặc Ctrl.
ComboBox Là sự kết hợp giữa một ô nhập và một ListBox. Tuy nhiên,
ListBox chỉ được hiển thị khi người dùng cần bằng cách nhấn
vào mũi tên sổ xuống ngay bên phải ô nhập.
Bảng 2.1. Một số điều khiển Windows Form thông dụng
Trang 87
Giáo trình lập trình cơ sở dữ liệu
Thực hiện các tuần tự các bước sau để tạo ứng dụng
- Tạo một ứng dụng mới và đặt tên cho nó là DataGridViewApplication.
- Thay đổi tên Form1 thành UsingDataGridView
Trang 88
Giáo trình lập trình cơ sở dữ liệu
- Thay đổi kích thước của DataGridView bằng cách nhấp chuột lên 1 trong 8 nút
vuông xung quanh và kéo rê chuột để được Form kết quả như hình 2.4.
Trang 89
Giáo trình lập trình cơ sở dữ liệu
- Mở kết nối tới cơ sở dữ liệu Northwind: Nhắp phải Data Connections, chọn
Add Connection (hình 2.5). Nhập thông tin tên máy chạy SQL Server, tên cơ
sở dữ liệu, tên đăng nhập và mật khẩu để truy cập vào cơ sở dữ liệu Northwind
(hình 2.6).
- Nhấp vào nút có hình tam giác ở phía góc trên bên phải của DataGridView,
trong mục Choose Data Source, nhấp chọn mũi tên từ ComboBox, chọn Add
Project Data Source (hình 2.7).
- Trong hộp thoại Data Source Configuration Wizard, chọn Database và nhấn nút
Next (hình 2.8).
- Ở bước tiếp, trong mục Which data connect… chọn cơ sở dữ liệu muốn kết
nối: Northwind. Nhấn nút Next. Giữ nguyên giá trị mặc định. Bấm Next.
Trang 90
Giáo trình lập trình cơ sở dữ liệu
- Trong cửa sổ Choose Your Database Objects, chọn nút Tables đánh dấu
chọn bảng Customers. Nhấn nút Finish.
Trang 91
Giáo trình lập trình cơ sở dữ liệu
- Giao diện của DataGridView được thay đổi, trong đó, dòng đầu tiên chứa tên
các cột có trong bảng Customers của cơ sở dữ liệu Northwind. Ngoài ra, bên
dưới màn hình thiết kế Form xuất hiện thêm 3 đối tượng mới.
Hình 2.11. DataGridView sau khi được kết nối tới cơ sở dữ liệu
Trong đó:
northwindDataSet: là một DataSet chứa bảng dữ liệu trả về từ cơ sở dữ liệu.
customersTableAdapter: là một đối tượng DataAdapter dùng để chuyển dữ liệu
từ cơ sở dữ liệu về DataSet.
customersBindingSource: là đối tượng dùng để chứa và hiển thị kết quả lên
DataGridView.
- Nhấn F5 để chạy chương trình và xem kết quả.
Lưu ý:
Trong trường hợp không muốn lấy dữ liệu trên tất cả các cột, ở cửa sổ Choose
Your Database Objects, chọn tên bảng muốn lấy dữ liệu rồi đánh dấu chọn vào
các cột của bảng mà bạn muốn lấy dữ liệu.
Để DataGridView tự động thay đổi kích thước khi Form được phóng to hay thu
nhỏ, bạn đặt giá trị thuộc tính Dock cho DataGridView là Full hoặc chọn giá trị
thuộc tính Anchor của DataGridView là Top, Left, Right, Bottom.
Trang 92
Giáo trình lập trình cơ sở dữ liệu
Chương này trình bày một cách nhìn tổng quát về các lớp của ADO.NET. Phần
cuối chương hướng dẫn chi tiết các bước để kết nối tới một cơ sở dữ liệu, lưu trữ các
dòng ở máy cục bộ, ngắt kết nối tới cơ sở dữ liệu và sau đó đọc, xử lý dữ liệu trên các
dòng đó ngay cả khi kết nối đã bị ngắt.
ADO.NET cho phép tương tác trực tiếp với cơ sở dữ liệu thông qua các đối tượng
thuộc nhóm lớp kết nối. Các đối tượng này cho phép bạn kết nối tới cơ sở dữ liệu và
thực thi các lệnh SQL thông qua một kết nối trực tiếp tới cơ sở dữ liệu. Nhóm lớp kết
nối cũng được chia làm nhiều tập con, phụ thuộc vào cơ sở dữ liệu mà bạn sử dụng.
ADO.NET cũng cho phép bạn làm việc trong điều kiện kết nối đã bị ngắt: Khi thực
hiện điều này, thông tin từ cơ sở dữ liệu được lưu trong bộ nhớ của máy tính cục bộ
mà chương trình đang chạy. Để lưu trữ những thông tin này, ta dùng các đối tượng
thuộc nhóm lớp không kết nối.
Các lớp thuộc nhóm lớp kết nối được dùng cho hệ quản trị cơ sở dữ liệu SQL
Server gồm: SqlConnection, SqlCommand, SqlDataReader, SqlDataAdapter,
SqlTransaction. Đối tượng SqlConnection dùng để kết nối tới cơ sở dữ liệu SQL
Server. Đối tượng SqlCommand dùng để biểu diễn một lệnh SQL hay một thủ tục và
Trang 93
Giáo trình lập trình cơ sở dữ liệu
thực thi lệnh đó. Đối tượng SqlDataReader dùng để đọc các dòng nhận được từ cơ sở
dữ liệu SQL Server. Đối tượng SqlDataAdapter dùng để chuyển các dòng giữa
DataSet và cơ sở dữ liệu SQL Server. Cuối cùng, đối tượng SqlTransaction được dùng
để biểu diễn cho một giao dịch cơ sở dữ liệu trong cơ sở dữ liệu SQL Server.
Đối tượng DataSet được dùng để biểu diễn một bản sao thông tin được lưu trữ
trong cơ sở dữ liệu. Bạn cũng có thể dùng đối tượng DataSet để biểu diễn dữ liệu ở
định dạng XML. Một DataSet có thể chứa nhiều đối tượng sau: DataTable, DataRow,
DataColumn, DataRelation và DataView.
Đối tượng DataTable dùng để biểu diễn một bảng, DataRow để biểu diễn một dòng
và DataColumn để biểu diễn một cột của bảng. Đối tượng DataRelation được dùng để
biểu diễn mối quan hệ giữa hai DataTable. Nó được dùng để mô hình hóa quan hệ cha-
con giữa hai bảng trong cơ sở dữ liệu thực tế. Cuối cùng, DataView dùng để xem các
dòng cụ thể trong một DataTable thông qua một bộ lọc.
1. Lớp nào được xem là trung gian giữa nhóm lớp kết nối và nhóm lớp không kết nối.
2. Có thể dùng đối tượng OleDbConnection để kết nối tới cơ sở dữ liệu SQL Server
hay không? Vì sao?
3. Trình bày cú pháp của chuỗi chứa thông tin kết nối tới cơ sở dữ liệu. Cho ví dụ.
4. Phương thức nào được dùng để đổ dữ liệu từ cơ sở dữ liệu vào một đối tượng thuộc
nhóm lớp không kết nối (chẳng hạn như DataSet, DataTable)?
5. Trình bày các bước để đọc các dòng từ một hay nhiều bảng trong cơ sở dữ liệu.
6. Trình bày sơ lược ý nghĩa của mô hình các lớp không kết nối trong hình 2.2.
7. Hãy thay đổi chương trình ví dụ trong phần kết hợp Windows Form với ADO.NET
để chỉ hiển thị các cột CustomerID, CompanyName, ContactName, ContactTitle,
Address, Country của bảng Customers trong cơ sở dữ liệu Northwind.
Trang 94
Giáo trình lập trình cơ sở dữ liệu
3. CHƯƠNG 3
KẾT NỐI CƠ SỞ DỮ LIỆU
Chương này trình bày chi tiết cách sử dụng lớp Connection để kết nối đến một cơ
sở dữ liệu. Có 3 lớp Connection thường được dùng: SqlConnection, OledbConnection
và OdbcConnection. Đối tượng SqlConnection được sử dụng để tạo một kết nối tới cơ
sở dữ liệu SQL Server. Đối tượng OledbConnection dùng để kết nối tới cơ sở dữ liệu
có hỗ trợ chuẩn OLE DB như Oracle hay Access. Cuối cùng, đối tượng
OdbcConnection dùng để kết nối tới cơ sở dữ liệu có hỗ trợ chuẩn ODBC. Như vậy,
muốn tương tác với cơ sở dữ liệu, trước hết, phải tạo một kết nối thông qua đối tượng
Connection.
Trang 95
Giáo trình lập trình cơ sở dữ liệu
Trong đó, connectionString là một chuỗi chứa thông tin chi tiết về kết nối cơ sở dữ
liệu. Để tạo một đối tượng SqlConnection, sử dụng đoạn mã sau:
mySqlConnection.ConnectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
Sau đó, gán chuỗi thông tin về kết nối cơ sở dữ liệu cho thuộc tính ConnectionString
của đối tượng mySqlConnection. Ví dụ:
mySqlConnection.ConnectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
Trong đó:
server: là tên của máy tính đang chạy SQL Server.
database: là tên cơ sở dữ liệu muốn kết nối.
uid: là tên người dùng cơ sở dữ liệu.
pwd: là mật khẩu tương ứng của người dùng.
Vì lý do bảo mật, bạn không nên đặt tên người dùng và mật khẩu trực tiếp vào
chuỗi kết nối. Thay vào đó, chương trình có thể yêu cầu người sử dụng thiết lập tên và
mật khẩu trước khi chạy hoặc có thể sử dụng chế độ bảo mật tích hợp (intergrated
security). Một điều nữa cần nhớ là ta chỉ có thể gán giá trị cho thuộc tính
ConnectionString khi kết nối đã đã được đóng.
Ta có thể thiết lập thêm tùy chọn thời gian chờ kết nối (connection timeout) để chỉ
ra số giây mà phương thức Open() chờ cho đến khi thiết lập được một kết nối tới cơ sở
dữ liệu. Để làm được điều này, chỉ cần bổ sung thêm một giá trị connection timeout
vào chuỗi kết nối như sau.
string connectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa;
connection timeout=10";
Thời gian chờ mặc định cho một kết nối là 15 giây. Nếu giá trị này được đặt là 0 thì
kết nối sẽ có thời gian chờ là vô hạn. Vì thế, không nên đặt giá trị connection timeout
là 0.
Trước khi bắt đầu đăng nhập vào Windows, bạn cần cung cấp tên đăng nhập và
mật khẩu. Trong trường hợp dùng chế độ bảo mật tích hợp (integrated security), ta có
thể bỏ qua tên đang nhập và mật khẩu trong chuỗi kết nối và sử dụng luôn các thông
tin đăng nhập trước đó để kết nối tới cơ sở dữ liệu SQL Server. Ta có thể sử dụng
integrated security trong ứng ựng bằng cách thêm đoạn intergrated security=SSPI vào
chuỗi kết nối. Ví dụ:
string connectionString =
"server=localhost;database=Northwind;intergrated security=SSPI;";
Để ý thấy trong chuỗi kết nối này không có tên người dùng cơ sở dữ liệu và mật
khẩu. Thay vào đó, tên và mật khẩu khi người dùng đăng nhập vào hệ điều hành sẽ
được gửi sang SQL Server. Sau đó, SQL Server kiểm tra trong danh sách người dùng
của nó xem thử bạn có được quyền truy xuất cơ sở dữ liệu hay không.
Trang 97
Giáo trình lập trình cơ sở dữ liệu
trong đó:
data source: là tên cơ sở dữ liệu cần kết nối.
provider: là một chuẩn của trình xử lý giao dịch trong cơ sở dữ liệu. Với
Access, giá trị này được đặt là Microsoft.Jet.OLEDB.4.0.
databaseFile: là đường dẫn đến tập tin chứa cơ sở dữ liệu cần kết nối.
Ví dụ: chuỗi kết nối sau được dùng để kết nối tới cơ sở dữ liệu Northwind lưu trong
tập tin Northwind.mdb, quản lý bởi Microsoft Office Access.
string connectionString =
"provider=Microsoft.Jet.OLEDB.4.0;" +
"data source=F:\\Program Files\\Microsoft
Office\\Office\\Samples\\Northwind.mdb";
Để tạo một đối tượng OleDbConnection với chuỗi kết nối cho trước, sử dụng đoạn mã
sau:
OleDbConnection myOleDbConnection =
new OleDbConnection(connectionString);
Ví dụ sau minh họa cách sử dụng một đối tượng OleDbConnection để kết nối tới
cơ sở dữ liệu Northwind quản lý bởi MS Access và lấy một dòng từ bảng Customers.
Ngoài ra, ví dụ này còn sử dụng hai đối tượng khác là OleDbCommand và
OleDbDataAdapter để thực thi truy vấn và đọc kết quả trả về từ cơ sở dữ liệu
Northwind.
Trang 98
Giáo trình lập trình cơ sở dữ liệu
using System;
using System.Data;
using System.Data.OleDb;
class OleDbConnectionAccess
{
public static void Main()
{
// formulate a string containing the details of the
// database connection
string connectionString =
"provider=Microsoft.Jet.OLEDB.4.0;" +
"data source=F:\\Program Files\\Microsoft
Office\\Office\\Samples\\Northwind.mdb";
OleDbConnection myOleDbConnection =
new OleDbConnection(connectionString);
myOleDbConnection.Open();
myOleDbDataReader.Read();
Console.WriteLine("myOleDbDataReader[\"CustomerID\"] = " +
myOleDbDataReader["CustomerID"]);
Console.WriteLine("myOleDbDataReader[\"CompanyName\"] = " +
myOleDbDataReader["CompanyName"]);
Console.WriteLine("myOleDbDataReader[\"ContactName\"] = " +
myOleDbDataReader["ContactName"]);
Console.WriteLine("myOleDbDataReader[\"Address\"] = " +
myOleDbDataReader["Address"]);
Trang 99
Giáo trình lập trình cơ sở dữ liệu
myOleDbDataReader.Close();
myOleDbConnection.Close();
}
}
myOleDbDataReader["CustomerID"] = ALFKI
myOleDbDataReader["CompanyName"] = Alfreds Futterkiste
myOleDbDataReader["ContactName"] = Maria Anders
myOleDbDataReader["Address"] = Obere Str. 57
Trong đó:
OracleNetServiceName: là tên của dịch vụ Oracle Net cho cơ sở dữ liệu.
Oracle Net là một thành phần của phần mềm cho phép bạn kết nối tới một cơ sở
dữ liệu qua mạng. Bạn cần phải liên hệ với người quản trị cơ sở dữ liệu để biết
tên của dịch vụ Oracle Net này.
User ID: là tên của người dùng được phép truy cập tới cơ sở dữ liệu.
Password: là mật khẩu tương ứng của người dùng.
Ví dụ sau tạo một chuỗi kết nối với tên là connectionString theo định dạng trên.
Sau đó, dùng lớp OleDbConnection để tạo một kết nối tới một cơ sở dữ liệu Oracle.
string connectionString =
"provider=MSDAORA;data source=ORCL;user id=SCOTT;password=TIGER";
OleDbConnection myOleDbConnection =
new OleDbConnection(connectionString);
Tên người dùng SCOTT và mật khẩu TIGER là những giá trị mặc định khi truy
xuất đến những cơ sở dữ liệu mẫu của Oracle.
Trang 100
Giáo trình lập trình cơ sở dữ liệu
mySqlConnection.Open();
Sau khi mở kết nối và thực hiện các giao dịch với cơ sở dữ liệu, bạn cần phải đóng
kết nối và giải phóng các tài nguyên bằng cách gọi phương thức Close() của đối tượng
Connection.
mySqlConnection.Close();
Ví dụ sau minh họa cách kết nối tới cơ sở dữ liệu Northwind của SQL Server sử
dụng đối tượng SqlConnection và hiển thị giá trị của một vài thuộc tính của nó.
Chương trình 3.2: Dùng SqlConnection để kết nối cơ sở dữ liệu SQL Server
/*
MySqlConnection.cs illustrates how to use a
SqlConnection object to connect to a SQL Server database
*/
using System;
using System.Data;
using System.Data.SqlClient;
class MySqlConnection
{
public static void Main()
{
// formulate a string containing the details of the
// database connection
string connectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
SqlConnection mySqlConnection =
new SqlConnection(connectionString);
mySqlConnection.Open();
Console.WriteLine("mySqlConnection.ConnectionString = " +
mySqlConnection.ConnectionString);
Console.WriteLine("mySqlConnection.ConnectionTimeout = " +
mySqlConnection.ConnectionTimeout);
Console.WriteLine("mySqlConnection.Database = " +
mySqlConnection.Database);
Console.WriteLine("mySqlConnection.DataSource = " +
mySqlConnection.DataSource);
Console.WriteLine("mySqlConnection.PacketSize = " +
mySqlConnection.PacketSize);
Console.WriteLine("mySqlConnection.ServerVersion = " +
mySqlConnection.ServerVersion);
Console.WriteLine("mySqlConnection.State = " +
Trang 101
Giáo trình lập trình cơ sở dữ liệu
mySqlConnection.State);
Console.WriteLine("mySqlConnection.WorkstationId = " +
mySqlConnection.WorkstationId);
mySqlConnection.Close();
}
}
Trong ví dụ này, một tổ hợp được tạo ra để chứa tối thiểu 5 và tối đa 10 đối tượng
SqlConnection. Nếu cố gắng mở thêm một đối tượng SqlConnection mới trong khi tổ
Trang 102
Giáo trình lập trình cơ sở dữ liệu
hợp đã đầy, yêu cầu mở sẽ phải chờ cho đến khi có một kết nối cũ bị đóng lại và lúc
đó, kết nối vừa bị đóng sẽ được trả về yêu cầu mới. Nếu thời gian chờ lớn hơn số giây
được quy định trong thuộc tính ConnectionTimeout, một lỗi sẽ phát sinh.
Ví dụ sau minh họa việc sử dụng tổ hợp kết nối nhằm tiết kiệm thời gian mở một
kết nối mới dựa trên các kết nối đã đóng trong tổ hợp.
using System;
using System.Data;
using System.Data.SqlClient;
class ConnectionPooling
{
public static void Main()
{
// create a SqlConnection object to connect to the database,
// setting max pool size to 10 and min pool size to 5
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa;" +
"max pool size=10;min pool size=5"
);
mySqlConnection.Open();
Trang 103
Giáo trình lập trình cơ sở dữ liệu
Console.WriteLine("mySqlConnection.State = " +
mySqlConnection.State);
mySqlConnection.Close();
}
}
}
count = 2
Milliseconds = 0
mySqlConnection.State = Open
count = 3
Milliseconds = 0
mySqlConnection.State = Open
count = 4
Milliseconds = 0
mySqlConnection.State = Open
count = 5
Milliseconds = 0
mySqlConnection.State = Open
count = 6
Milliseconds = 0
mySqlConnection.State = Open
count = 7
Milliseconds = 0
mySqlConnection.State = Open
count = 8
Milliseconds = 0
mySqlConnection.State = Open
count = 9
Milliseconds = 0
mySqlConnection.State = Open
count = 10
Milliseconds = 0
mySqlConnection.State = Open
Với ví dụ này, ta có thể thấy thời gian để mở kết nối đầu tiên là khá dài so với các
kết nối sau đó. Sở dĩ có điều này là bởi vì kết nối đầu tiên sẽ phải tạo ra một kết nối
Trang 104
Giáo trình lập trình cơ sở dữ liệu
thực sự tới cơ sở dữ liệu. Khi kết nối này bị đóng, nó được lưu vào tổ hợp kết nối. Khi
có yêu cầu mở một kết nối mới, nó được lấy từ tổ hợp và việc này được thực hiện rất
nhanh.
Với một ứng dụng tương đối lớn và phức tạp hoặc bạn muốn sử dụng cùng một kết
nối ở nhiều nơi trong chương trình, cần phải kiểm tra trạng thái của kết nối trước khi
mở. Điều này thực sự cần thiết vì nếu gọi phương thức Open() trên một kết nối đang
mở sẽ phát sinh ra lỗi. Việc kiểm tra trạng thái kết nối có thể thực hiện như sau:
if (mySqlConnection.State == ConnectionState.Closed)
{
mySqlConnection.Open();
}
Trang 105
Giáo trình lập trình cơ sở dữ liệu
Tham số thứ nhất là đối tượng phát sinh ra sự kiện và có kiểu object. Lớp
System.Object đóng vai trò là lớp cơ sở cho mọi lớp khác. Nói cách khác, mọi lớp
khác đều được dẫn xuất từ lớp này.
Tham số thứ hai là một đối tượng được tạo từ một lớp dẫn xuất từ lớp
System.EventArgs. Lớp EventArgs là lớp cơ sở cho dữ liệu đi kèm trong sự kiện và
biểu diễn chi tiết về sự kiện đó. Trong trường hợp sự kiện StateChange, tham số thứ
hai này là một đối tượng của lớp StateChangeEventArgs.
Ví dụ sau định nghĩa một phương thức có tên StateChangeHandler để xử lý sự kiện
StateChange. Để biết trạng thái ban đầu và trạng thái hiện tại của kết nối, ta sử dụng
thuộc tính OriginalState và CurrentState của tham số thứ hai.
public static void StateChangeHandler(
object mySender, StateChangeEventArgs myEvent)
{
Console.WriteLine(
"mySqlConnection State has changed from " +
myEvent.OriginalState + "to " +
myEvent.CurrentState
);
}
Để quản lý một sự kiện, bạn phải đăng ký trình xử lý cho sự kiện đó. Đoạn mã sau
đăng ký phương thức StateChangeHandler() làm trình xử lý cho sự kiện StateChange
của đối tượng SqlConnection.
mySqlConnection.StateChange +=
new StateChangeEventHandler(StateChangeHandler);
Bất cứ khi nào sự kiện StateChange phát sinh, phương thức StateChangeHandler sẽ
được gọi để cho biết trạng thái cũ và trạng thái hiện hành của kết nối. Đoạn mã sau
minh họa cách sử dụng sự kiện StateChange của lớp SqlConnection.
using System;
using System.Data;
using System.Data.SqlClient;
class StateChange
{
// define the StateChangeHandler() method to handle the
// StateChange event
Trang 106
Giáo trình lập trình cơ sở dữ liệu
);
}
mySqlConnection.StateChange +=
new StateChangeEventHandler(StateChangeHandler);
Console.WriteLine("Calling mySqlConnection.Open()");
mySqlConnection.Open();
Console.WriteLine("Calling mySqlConnection.Close()");
mySqlConnection.Close();
}
}
Trang 107
Giáo trình lập trình cơ sở dữ liệu
);
}
Nếu sử dụng trình cung cấp OLE DB, ODBC thay cho SQL, bạn thay SqlInfo-
MessageEventArgs bởi OleDbInfoMessageEventArgs, OdbcInfoMessageEventArgs.
Ví dụ sau minh họa cách sử dụng sự kiện InfoMessage. Chương trình này có sử
dụng phương thức ExecuteNonQuery() của đối tượng SqlCommand để gửi câu lệnh
PRINT và RAISERROR tới cơ sở dữ liệu.
class InfoMessage
{
// define the InfoMessageHandler() method to handle the
// InfoMessage event
mySqlConnection.InfoMessage +=
new SqlInfoMessageEventHandler(InfoMessageHandler);
// open mySqlConnection
mySqlConnection.Open();
Trang 108
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"RAISERROR('This is the message from the RAISERROR statement', 10, 1)";
mySqlCommand.ExecuteNonQuery();
// close mySqlConnection
mySqlConnection.Close();
}
}
3.8. Tạo đối tượng kết nối dùng Visual Studio .NET
Để tạo một đối tượng SqlConnection bằng giao diện trên môi trường Visual Studio
.Net, bạn kéo đối tượng SqlConnection từ thẻ Data của thanh Toolbox vào Form. Đối
tượng SqlConnection này cho phép kết nối một cơ sở dữ liệu. Tương tự, bạn cũng có
thể kéo một đối tượng OleDbConnection từ Toolbox vào Form để kết nối tới cơ sở dữ
liệu hỗ trợ OLE DB.
Hình 3.1 dưới đây cho thấy Form có chứa một đối tượng SqlConnection. Đối tượng
này được gán tên mặc định là sqlConnection1 và nằm ở phần dưới của khung thiết kế
Form.
Nếu trong thẻ Data của Toolbox không có SqlConnection, bạn thực hiện các bước sau:
- Nhấp phải chuột vào Toolbox, chọn Choose Items…
- Trong thẻ .NET Framework Components của cửa sổ Choose Toolbox Items,
đánh dấu chọn vào các mục SqlCommand, SqlCommandBuilder,
SqlConnection, SqlDataAdapter và SqlDataSource. Nhấn OK.
Một khi đối tượng SqlConnection đã được
tạo, nó xuất hiện ở khung nhỏ bên dưới Form.
Khung này được dùng để chứa các thành phần
“không hiển thị” của Form, chẳng hạn như
SqlConnection, SqlCommand. Những đối
tượng này được xem là “không hiển thị”vì bạn
không thể thấy chúng xuất hiện trên Form lúc
chạy chương trình. Tuy nhiên, bạn vẫn có thể
thao tác một cách trực quan với chúng khi
đang ở chế độ thiết kế Form.
Ở bên phải Form là cửa sổ Properties, dùng để
đặt giá trị cho các thuộc tính của đối tượng
SqlConnection. Hình 3.3. Tạo chuỗi
ConnectionString
Trang 109
Giáo trình lập trình cơ sở dữ liệu
workstation id là tên của máy tính đang chạy ứng dụng. persist security info có
kiểu logic Bool điều khiển các thông tin liên quan đến bảo mật (như mật khẩu) có
được lấy từ kết nối đã mở trước đó hay không. Thông thường, giá trị này được đặt là
False.
Trang 111
Giáo trình lập trình cơ sở dữ liệu
Trang 112
Giáo trình lập trình cơ sở dữ liệu
- Thêm đoạn mã sau vào hàm xử lý sự kiện StateChange để được kết quả như hình
3.8:
private void sqlConnection1_StateChange(object sender,
StateChangeEventArgs e)
{
Console.WriteLine( "State has changed from " +
e.OriginalState + "to " +
e.CurrentState );
}
Trang 113
Giáo trình lập trình cơ sở dữ liệu
Trong chương này, bạn đã được học cách kết nối đến một cơ sở dữ liệu. Có 3 lớp kết
nối Connection là SqlConnection, OleDbConnection và OdbcConnection. Đối tượng
SqlConnection dùng để kết nối tới cơ sở dữ liệu SQL Server. Đối tượng
OleDbConnection (OdbcConnection) dùng để kết nối tới các cơ sở dữ liệu có hỗ trợ
OLE DB (ODBC) như Oracle hay Access. Mọi liên lạc giữa trình ứng dụng với cơ sở
dữ liệu phải thông qua một đối tượng Connection.
ADO.NET tự động lưu các kết nối cơ sở dữ liệu thành một tổ hợp. Khi một kết nối
bị ngắt, nó không thực sự bị đóng mà thay vào đó, chuyển sang trạng thái chờ và được
đánh dấu là chưa sử dụng, lưu trữ trong một tổ hợp và sẵn sàng để sử dụng lại. Sau đó,
nếu bạn cần kết nối tới một cơ sở dữ liệu với thông tin chi tiết trong chuỗi kết nối giống
với thông tin của một Connection đang rỗi được lưu trong tổ hợp thì Connection trong
tổ hợp đó sẽ được dùng để kết nối tới cơ sở dữ liệu thay vì phải tạo mới. Tổ hợp kết nối
làm cải thiện đáng kể đặc trưng của ứng dụng vì không phải chờ để tạo mới một kết nối
tới cơ sở dữ liệu.
Trang 114
Giáo trình lập trình cơ sở dữ liệu
Bạn sử dụng thuộc tính State của đối tượng Connection để lấy trạng thái hiện tại của
kết nối cơ sở dữ liệu. Thuộc tính State trả về một hằng số có kiểu enum tên là
ConnectionState.
Sự kiện StateChange của đối tượng Connection dùng để quản lý các thay đổi về
trạng thái của đối tượng Connection. Bạn sử dụng sự kiện InfoMessage của đối tượng
Connection để quản lý các cảnh báo và thông điệp trả về từ cơ sở dữ liệu. Những thông
báo này có thể được sinh ra bởi lệnh PRINT và RAISERROR của SQL Server.
Để tạo một đối tượng SqlConnection bằng Visual Studio .Net, bạn kéo một đối
tượng SqlConnection từ thẻ Data của Toolbox vào Form. Sau đó, mở khung Properties
của SqlConnection để thiết lập giá trị các thuộc tính kết nối.
1. Liệt kê các thuộc tính của chuỗi kết nối. Giải thích ý nghĩa của các thuộc tính đó.
2. Giải thích các trạng thái kết nối. Vẽ mô hình chuyển đổi giữa các trạng thái đó.
3. Đối tượng Connection có những thuộc tính, phương thức và sự kiện nào. Giải thích
ý nghĩa của chúng và cho ví dụ.
4. Trình bày ưu, nhược điểm của tổ hợp kết nối. Giải thích cách tạo và sử dụng lại kết
nối trong tổ hợp.
5. Tìm chuỗi kết nối để tạo đối tượng Connection kết nối tới một cơ sở dữ liệu
MySQL. Muốn tạo kết nối này, ta phải dùng lớp Connection nào? Vì sao?
6. Trình bày các bước để tạo đối tượng Connection kết nối tới cơ sở dữ liệu đã tạo
trong bài tập 7 của chương 1.
Trang 115
Giáo trình lập trình cơ sở dữ liệu
4. CHƯƠNG 4
THỰC THI LỆNH TRUY VẤN CƠ SỞ DỮ LIỆU
Các lệnh truy vấn cơ sở dữ liệu được thực thi bởi đối tượng Command. Lớp
Command cũng là một trong những lớp kết nối (managed provider). Có ba lớp
Command: SqlCommand, OleDbCommand và OdbcCommand. Đối tượng Command
được dùng để thực thi một lệnh truy vấn SQL như SELECT, INSERT, UPDATE hay
DELETE. Nó cũng có thể được dùng để gọi một thủ tục (Stored Procedure) hay lấy tất
cả các dòng, các cột từ một bảng cụ thể - lệnh này còn được gọi là TableDirect. Đối
tượng Command liên lạc với cơ sở dữ liệu thông qua một đối tượng Connection.
Trang 116
Giáo trình lập trình cơ sở dữ liệu
Trang 117
Giáo trình lập trình cơ sở dữ liệu
Hầu hết các phương thức và thuộc tính này cũng được dùng trong các lớp
OleDbCommand và OdbcCommand.
4.2.1. Tạo đối tượng SqlCommand dùng phương thức khởi tạo
Lớp SqlCommand có 4 phương thức tạo lập sau:
SqlCommand()
SqlCommand(string commandText)
SqlCommand(string commandText, SqlConnection mySqlConnection)
SqlCommand(string commandText, SqlConnection mySqlConnection,
SqlTransaction mySqlTransaction)
Trong đó:
commandText: chứa lệnh truy vấn SQL, tên thủ tục hay tên bảng muốn lấy dữ
liệu.
mySqlConnection: là đối tượng kết nối, phương tiện liên lạc với cơ sở dữ liệu.
mySqlTransaction: là đối tượng giao dịch cơ sở dữ liệu SQL.
Trước khi sử dụng đối tượng SqlCommand, ta cần tạo ra một kết nối để liên lạc với
cơ sở dữ liệu SQL Server.
mySqlConnection.ConnectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
Tiếp theo, tạo một đối tượng lệnh SqlCommand và gắn với kết nối
SqlCommand mySqlCommand = new SqlCommand();
mySqlCommand.Connection = mySqlConnection;
Đối tượng SqlCommand sẽ phải sử dụng một đối tượng SqlConnection để liên lạc
với cơ sở dữ liệu. Thuộc tính CommandType của đối tượng Command xác định cách
mà lệnh truy vấn được thực thi. Thuộc tính này nhận các giá trị trong enum
CommandType (System.Data.CommandType).
VALUE DESCRIPTION
Text Chỉ ra lệnh truy vấn là một lệnh SQL bình thường. Đây là giá
trị mặc định.
Trang 118
Giáo trình lập trình cơ sở dữ liệu
VALUE DESCRIPTION
StoredProcedure Chỉ ra rằng, lệnh truy vấn cần thực thi là một Stored
Procedure.
TableDirect Chỉ ra tên của bảng muốn lấy tất cả các dòng, các cột. Các
đối tượng SqlCommand không hỗ trợ TableDirect. Bạn phải dùng
một đối tượng của các lớp Command khác để thực hiện.
Bảng 4.3. Các hằng số của CommandType
Chuỗi lệnh truy vấn cần thực thi được gửi vào đối tượng Command qua thuộc tính
CommandText. Ví dụ sau gán một lệnh SELECT cho đối tượng Command để lấy ra 10
khách hàng đầu tiên trong bảng Customers.
mySqlCommand.CommandText =
"SELECT TOP 10 CustomerID, CompanyName, ContactName, Address" +
"FROM Customers ORDER BY CustomerID";
Cũng có thể gửi lệnh truy vấn và đối tượng kết nối vào đối tượng Command cùng
một lúc khi tạo đối tượng Command như sau:
SqlCommand mySqlCommand = new SqlCommand(
"SELECT TOP 5 CustomerID, CompanyName, ContactName, Address " +
"FROM Customers ORDER BY CustomerID",
mySqlConnection);
Trang 119
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"SELECT TOP 5 CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"ORDER BY CustomerID";
mySqlConnection.Open();
SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();
Lưu ý: Phương thức Open() của đối tượng SqlConnection chỉ được gọi ngay trước
khi gọi hàm thực thi một truy vấn (ở đây là phương thức ExecuteReader của đối tượng
SqlCommand). Bằng cách này, bạn có thể giảm tối đa chi phí thời gian kết nối cơ sở dữ
liệu và do đó tối ưu hóa được các tài nguyên cơ sở dữ liệu.
Tập kết quả trả về bởi đối tượng SqlCommand được lưu trong một đối tượng
SqlDataReader có tên là mySqlDataReader. Bạn có thể đọc lần lượt các dòng từ
mySqlDataReader bằng phương thức Read().
Phương thức Read trả về một giá trị Boolean. Giá trị đúng (true) có nghĩa là còn một
dòng khác trong tập kết quả chưa được đọc. Giá trị sai (false) có nghĩa là tất cả các dòng
đã được đọc.
Ta cũng có thể đọc giá trị trên một cột cụ thể của một dòng từ đối tượng DataReader
bằng cách đặt tên cột hoặc một số nguyên dương (chỉ ra thứ tự tương ứng của cột đó
trong tập các cột, giá trị 0 tương ứng với cột đầu tiên) trong cặp dấu ngoặc vuông [ ]
ngay sau đối tượng DataReader. Chẳng hạn, để đọc giá trị trên cột CustomerID của
dòng hiện tại, ta viết mySqlDataReader[“CustomerID”] hoặc mySqlDataReader[ 0 ].
Vì mỗi lần, phương thức Read() chỉ đọc được một dòng nên để đọc hết tất cả các
dòng, ta dùng một vòng lặp để đọc từng dòng như sau:
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\" CustomerID\"] = " +
mySqlDataReader["CustomerID"]);
Trang 120
Giáo trình lập trình cơ sở dữ liệu
mySqlDataReader["CompanyName"]);
Ví dụ sau minh họa một chương trình hoàn chỉnh hướng dẫn cách sử dụng phương
thức ExecuteReader của đối tượng SqlCommand.
class ExecuteSelect
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 5 CustomerID, CompanyName, ContactName, Address "
+
"FROM Customers " +
"ORDER BY CustomerID";
mySqlConnection.Open();
Trang 121
Giáo trình lập trình cơ sở dữ liệu
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"CustomerID\"] = " +
mySqlDataReader["CustomerID"]);
Console.WriteLine("mySqlDataReader[\"CompanyName\"] = " +
mySqlDataReader["CompanyName"]);
Console.WriteLine("mySqlDataReader[\"ContactName\"] = " +
mySqlDataReader["ContactName"]);
Console.WriteLine("mySqlDataReader[\"Address\"] = " +
mySqlDataReader["Address"]);
Console.WriteLine();
}
mySqlDataReader["CustomerID"] = ANATR
mySqlDataReader["CompanyName"] = Ana Trujillo3 Emparedados y
mySqlDataReader["ContactName"] = Ana Trujillo
mySqlDataReader["Address"] = Avda. de la Constitución 2222
mySqlDataReader["CustomerID"] = ANTON
mySqlDataReader["CompanyName"] = Antonio Moreno Taquería
mySqlDataReader["ContactName"] = Antonio Moreno
mySqlDataReader["Address"] = Mataderos 2312
mySqlDataReader["CustomerID"] = AROUT
mySqlDataReader["CompanyName"] = Around the Horn
mySqlDataReader["ContactName"] = Thomas Hardy
mySqlDataReader["Address"] = 120 Hanover Sq.
mySqlDataReader["CustomerID"] = BERGS
mySqlDataReader["CompanyName"] = Berglunds snabbköp
mySqlDataReader["ContactName"] = Christina Berglund
mySqlDataReader["Address"] = Berguvsvägen 8
Trang 122
Giáo trình lập trình cơ sở dữ liệu
Với lời gọi hàm này, mặc dù lệnh SELECT lấy tất cả các dòng trong bảng Products
nhưng đối tượng mySqlDataReader chỉ cho phép đọc dòng đầu tiên.
Trang 123
Giáo trình lập trình cơ sở dữ liệu
using System.Data;
using System.Data.SqlClient;
class SingleRowCommandBehavior
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice " +
"FROM Products";
mySqlConnection.Open();
SqlDataReader mySqlDataReader =
mySqlCommand.ExecuteReader(CommandBehavior.SingleRow);
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"ProductID\"] = " +
mySqlDataReader["ProductID"]);
Console.WriteLine("mySqlDataReader[\"ProductName\"] = " +
mySqlDataReader["ProductName"]);
Console.WriteLine("mySqlDataReader[\"QuantityPerUnit\"]="
+
mySqlDataReader["QuantityPerUnit"]);
Console.WriteLine("mySqlDataReader[\"UnitPrice\"] = " +
mySqlDataReader["UnitPrice"]);
}
mySqlDataReader.Close();
mySqlConnection.Close();
}
}
Trang 124
Giáo trình lập trình cơ sở dữ liệu
Tiếp theo, ta gọi phương thức ExecuteReader với giá trị tham số là
CommandBehavior.SchemaOnly để cho đối tượng Command biết chỉ cần trả về thông
tin của các cột.
SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
Trong ví dụ này, vì các cột ProductID, ProductName và UnitPrice của bảng Products
được dùng trong câu lệnh SELECT nên kết quả trả về chính là thông tin của những cột
này thay cho giá trị tương ứng trên những cột đó.
Để lấy thông tin của các cột, ta gọi phương thức GetSchemaTable của đối tượng
SqlDataReader. Phương thức này trả về một đối tượng DataTable với các cột chứa
thông tin chi tiết về các cột tương ứng trong cơ sở dữ liệu.
DataTable myDataTable = productsSqlDataReader.GetSchemaTable();
Để truy cập các giá trị trong đối tượng DataTable, ta dùng một vòng lặp để hiển thị
tên các cột của DataTable và nội dung tương ứng của mỗi cột.
foreach (DataRow myDataRow in myDataTable.Rows)
{
foreach (DataColumn myDataColumn in myDataTable.Columns)
{
Console.WriteLine(myDataColumn + "= " +
myDataRow[myDataColumn]);
if (myDataColumn.ToString() == "ProviderType")
{
Console.WriteLine(myDataColumn + "= " +
((System.Data.SqlDbType)myDataRow[myDataColumn]));
}
}
}
Đoạn mã này dùng hai vòng lặp: vòng lặp ngoài duyệt qua các dòng (DataRow) của
DataTable còn vòng lặp bên trong duyệt qua các cột của DataRow hiện tại. Câu lệnh if
trong vòng lặp thứ hai dùng để kiểm tra xem cột (DataColumn) hiện tại có chứa
ProviderType hay không (tức là có phải cột chứa kiểu dữ liệu của cột tương ứng trong
cơ sở dữ liệu hay không).
Trang 125
Giáo trình lập trình cơ sở dữ liệu
ProviderType chứa một giá trị số cho biết kiểu dữ liệu của cột đó trong cơ sở dữ
liệu. Giá trị số này được ép sang kiểu liệt kê System.Data.SqlDbType. Enum này định
nghĩa các kiểu dữ liệu của SQL Server và được liệt kê trong bảng 4.9. Bằng cách ép giá
trị số ProviderType sang SqlDbType, ta có thể biết được tên kiểu dữ liệu thực sự của
cột.
Lần lặp đầu tiên của vòng lặp ngoài sẽ hiển thị tất cả các thông tin về cột đầu tiên.
Trong ví dụ này, các thông tin chi tiết về cột ProductID sẽ được xuất ra màn hình có
dạng sau:
ColumnName = ProductID
ColumnOrdinal = 0
ColumnSize = 4
NumericPrecision = 0
NumericScale = 0
IsUnique =
IsKey =
BaseCatalogName =
BaseColumnName = ProductID
BaseSchemaName =
BaseTableName =
DataType = System.Int32
AllowDBNull = False
ProviderType = 8
ProviderType = Int
IsAliased =
IsExpression =
IsIdentity = True
IsAutoIncrement = True
IsRowVersion =
IsHidden =
IsLong = False
IsReadOnly = True
Bảng sau cho biết ý nghĩa của các giá trị trên
VALUE DESCRIPTION
ColumnName Tên cột.
ColumnOrdinal Thứ tự của cột
ColumnSize Kích thước tối đa (số ký tự) của giá trị chứa trong cột. Với
các kiểu dữ liệu có chiều dài cố định như int, bigint,
ColumnSize là kích thước của kiểu dữ liệu đó.
NumericPrecision Số lượng chữ số được dùng để biểu diễn số có dấu chấm động
(như float, double). Số lượng chữ số này bao gồm cả số chữ
số bên trái và bên phải dấu chấm.
NumericScale Số lượng chữ số nằm bên phải dấu chấm trong một số dấu chấm
động.
IsUnique Là một giá trị Boolean true/false cho biết hai dòng khác
nhau có thể có cùng một giá trị trên cột hiện tại hay không.
IsKey Là một giá trị Boolean true/false cho biết cột hiện tại có
nằm trong tập khóa chính hay không.
BaseCatalogName Tên của danh mục trong cơ sở dữ liệu chứa cột hiện tại. Giá
trị mặc định của BaseCatalogName là null.
BaseColumnName Tên của cột trong cơ sở dữ liệu. Giá trị này khác với
Trang 126
Giáo trình lập trình cơ sở dữ liệu
VALUE DESCRIPTION
ColumnName nếu bạn có sử dụng tên khác (alias) cho cột hiện
tại. Giá trị mặc định của BaseColumnName là null.
BaseSchemaName Tên của lược đồ cơ sở dữ liệu chứa cột hiện tại. Giá trị mặc
định của BaseSchemaName là null.
BaseTableName Tên của bảng hay khung nhìn (View) trong cơ sở dữ liệu chứa
cột hiện tại. Giá trị mặc định của BaseTableName là null.
DataType Kiểu dữ liệu của .Net biểu diễn cột hiện tại.
AllowDBNull Giá trị Boolean true/false cho biết cột hiện tại có chấp
nhận giá trị null hay không.
ProviderType Cho biết kiểu dữ liệu của cột trong cơ sở dữ liệu.
IsAliased Giá trị Boolean true/false cho biết cột hiện tại có dùng một
tên khác (bí danh – alias) hay không.
IsExpression Giá trị Boolean true/false cho biết cột hiện tại chứa công
thức hay không.
IsIdentity Giá trị Boolean true/false cho biết cột hiện tại là cột định
danh (dùng để xác định giá trị trên các dòng khác).
IsAutoIncrement Giá trị Boolean true/false cho biết cột hiện tại chứa giá
trị được gán tự động khi thêm một dòng mới. Giá trị này cũng
được tăng tự động sau mỗi lần gán.
IsRowVersion Giá trị Boolean true/false cho biết cột hiện tại có chứa một
từ định danh cho dòng hay không (từ định danh này không thể
thay đổi).
IsHidden Giá trị Boolean true/false cho biết cột hiện tại có bị ẩn
hay không.
IsLong Giá trị Boolean true/false cho biết cột hiện tại có chứa đối
tượng kiểu nhị phân dài (BLOB – Binary Long Object) hay
không. Một BLOB chứa một chuỗi dữ liệu nhị phân dài.
IsReadOnly Giá trị Boolean true/false cho biết cột hiện tại có thể thay
đổi được hay không.
Bảng 4.6. Các thuộc tính của một cột trong cơ sở dữ liệu.
Đoạn chương trình sau minh họa các sử dụng CommandBehavior.SchemaOnly để hiển
thị thông tin chi tiết của các cột ProductID, ProductName và UnitPrice.
Chương trình 4.3: Đọc giá trị thuộc tính của các cột (Table schema)
/*
SchemaOnlyCommandBehavior.cs illustrates how to read a table schema
*/
using System;
using System.Data;
using System.Data.SqlClient;
class SchemaOnlyCommandBehavior
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
Trang 127
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"SELECT ProductID, ProductName, UnitPrice " +
"FROM Products " +
"WHERE ProductID = 1";
mySqlConnection.Open();
SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);
if (myDataColumn.ToString() == "ProviderType")
{
Console.WriteLine(myDataColumn + " = " +
((System.Data.SqlDbType)myDataRow[myDataColumn]));
}
}
}
productsSqlDataReader.Close();
mySqlConnection.Close();
}
}
Trang 128
Giáo trình lập trình cơ sở dữ liệu
DataType = System.Int32
AllowDBNull = False
ProviderType = 8
ProviderType = Int
IsAliased =
IsExpression =
IsIdentity = True
IsAutoIncrement = True
IsRowVersion =
IsHidden =
IsLong = False
IsReadOnly = True
Trang 129
Giáo trình lập trình cơ sở dữ liệu
IsHidden =
IsLong = False
IsReadOnly = False
Tiếp theo, ta tạo đối tượng OleDbCommand rồi đặt giá trị cho thuộc tính
CommandType của OleDbCommand là CommandType.TableDirect. Gán tên của bảng
muốn lấy dữ liệu cho thuộc tính CommandText.
OleDbCommand myOleDbCommand = myOleDbConnection.CreateCommand();
myOleDbCommand.CommandType = CommandType.TableDirect;
myOleDbCommand.CommandText = "Products";
Mở kết nối đến cơ sở dữ liệu và thực thi lệnh truy vấn bằng cách gọi phương thức
ExecuteReader.
myOleDbConnection.Open();
OleDbDataReader myOleDbDataReader =
myOleDbCommand.ExecuteReader();
Về mặt bản chất, SQL Server thực hiện câu lệnh SELECT * FROM Products để lấy
tất cả các dòng và các cột từ bảng Products.
Đoạn chương trình hoàn chỉnh sau minh họa cách thực thi lệnh TableDirect bằng
cách dùng phương thức ExecuteReader.
using System;
using System.Data;
using System.Data.OleDb;
Trang 130
Giáo trình lập trình cơ sở dữ liệu
class ExecuteTableDirect
{
public static void Main()
{
OleDbConnection myOleDbConnection =
new OleDbConnection(
"Provider=SQLOLEDB;server=localhost;database=Northwind;" +
"uid=sa;pwd=sa"
);
myOleDbCommand.CommandType = CommandType.TableDirect;
myOleDbCommand.CommandText = "Products";
myOleDbConnection.Open();
OleDbDataReader myOleDbDataReader =
myOleDbCommand.ExecuteReader();
Console.WriteLine("myOleDbDataReader[\"ProductID\"] = " +
myOleDbDataReader["ProductID"]);
Console.WriteLine("myOleDbDataReader[\"ProductName\"] = "
+
myOleDbDataReader["ProductName"]);
Console.WriteLine("myOleDbDataReader[\"QuantityPerUnit\"]="
+ myOleDbDataReader["QuantityPerUnit"]);
Console.WriteLine("myOleDbDataReader[\"UnitPrice\"] = " +
myOleDbDataReader["UnitPrice"]);
}
myOleDbDataReader.Close();
myOleDbConnection.Close();
}
}
Kết quả chạy chương trình (Chỉ lấy 5 dòng đầu tiên trong kết quả)
myOleDbDataReader["ProductID"] = 1
myOleDbDataReader["ProductName"] = Chai
myOleDbDataReader["QuantityPerUnit"] = 10 boxes x 20 bags
Trang 131
Giáo trình lập trình cơ sở dữ liệu
myOleDbDataReader["UnitPrice"] = 18
myOleDbDataReader["ProductID"] = 2
myOleDbDataReader["ProductName"] = Chang
myOleDbDataReader["QuantityPerUnit"] = 24 - 12 oz bottles
myOleDbDataReader["UnitPrice"] = 19
myOleDbDataReader["ProductID"] = 3
myOleDbDataReader["ProductName"] = Aniseed Syrup
myOleDbDataReader["QuantityPerUnit"] = 12 - 550 ml bottles
myOleDbDataReader["UnitPrice"] = 10
myOleDbDataReader["ProductID"] = 4
myOleDbDataReader["ProductName"] = Chef Anton's Cajun Seasoning
myOleDbDataReader["QuantityPerUnit"] = 48 - 6 oz jars
myOleDbDataReader["UnitPrice"] = 22
myOleDbDataReader["ProductID"] = 5
myOleDbDataReader["ProductName"] = Chef Anton's Gumbo Mix
myOleDbDataReader["QuantityPerUnit"] = 36 boxes
myOleDbDataReader["UnitPrice"] = 21.35
Tiếp theo, thực thi câu lệnh SELECT bằng cách gọi phương thức ExecuteScalar.
int returnValue = (int)mySqlCommand.ExecuteScalar();
Vì kết quả trả về có kiểu object nên ta phải ép kiểu về int trước khi lưu vào biến
resultValue. Đoạn mã sau minh họa chương trình hoàn chỉnh để đếm số dòng của bảng
Products.
Chương trình 4.5: Thực thi lệnh SELECT dùng phương thức ExecuteScalar
/*
ExecuteScalar.cs illustrates how to use the ExecuteScalar()
method to run a SELECT statement that returns a single value
*/
using System;
Trang 132
Giáo trình lập trình cơ sở dữ liệu
using System.Data;
using System.Data.SqlClient;
class ExecuteScalar
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
mySqlCommand.CommandText =
"SELECT COUNT(*) " +
"FROM Products";
mySqlConnection.Open();
Console.WriteLine("mySqlCommand.ExecuteScalar() = " +
returnValue);
mySqlConnection.Close();
}
}
Kết quả chạy chương trình (phụ thuộc vào số dòng trong bảng Products của cơ sở dữ
liệu Northwind được cài đặt kèm theo SQL Server trên từng máy tính):
mySqlCommand.ExecuteScalar() = 79
Bảng sau mô tả chi tiết các từ khóa sử dụng trong mệnh đề FOR XML.
KEYWORD DESCRIPTION
FOR XML Chỉ cho SQL Server biết dữ liệu trả về ở định dạng XML.
Trang 133
Giáo trình lập trình cơ sở dữ liệu
KEYWORD DESCRIPTION
RAW Có nghĩa là mỗi dòng trong tập dữ liệu kết quả tương ứng với một
XML Element. Giá trị của các cột sẽ là thuộc tính của XML
Element đó.
AUTO Có nghĩa là mỗi dòng trong tập kết quả trả về là một XML Element
với tên bảng được dùng làm tên chung cho XML Element.
EXPLICIT Chỉ ra rằng, lệnh SELECT có quan hệ cha con sẽ được dùng bởi SQL
Server để tạo tài liệu XML có cấu trúc phân cấp thích hợp.
XMLDATA Cho biết tài liệu DTD (Document Type Definition) sẽ được gắn kèm
trong dữ liệu XML trả về.
ELEMENTS Có nghĩa là các cột trả về ở dạng Element con của Element biểu
diễn cho hàng tương ứng. Ngược lại, các cột tương ứng với các
thuộc tính của Element biểu diễn một hàng. Sử dụng kết hợp tùy
chọn này với AUTO.
BINARY Cho biết dữ liệu nhị phân được trả về sau truy vấn sẽ được mã
BASE64 hóa ở dạng 64 bit. Nếu muốn nhận trực tiếp dữ liệu nhị phân, sử
dụng kết hợp với hai tùy chọn RAW và EXPLICIT. Ở chế độ AUTO, dữ
liệu nhị phân được trả về ở dạng tham chiếu.
Bảng 4.7. Các từ khóa trong mệnh đề FOR XML
Ví dụ sau gán một lệnh SELECT có sử dụng mệnh đề FOR XML AUTO cho thuộc
tính CommandText của đối tượng SqlCommand. Lệnh SELECT này được dùng để lấy 5
dòng đầu tiên của bảng Products ở dạng XML.
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID " +
"FOR XML AUTO";
Tiếp theo, gọi phương thức ExecuteXmlReader để thực thi câu lệnh SELECT. Kết
quả trả về được lưu trong một đối tượng XmlReader. Lớp XmlReader được định nghĩa
trong namespace System.Xml
XmlReader myXmlReader = mySqlCommand.ExecuteXmlReader();
Để đọc dữ liệu XML từ đối tượng XmlReader, ta dùng phương thức Read. Bạn có
thể kiểm tra xem đã đọc hết tất cả các dòng hay chưa bằng cách dùng thuộc tính EOF
(End Of File) của đối tượng XmlReader. EOF trả về giá trị true nếu đã đọc hết tất cả các
dòng, ngược lại, giá trị của thuộc tính này là false. Ngoài ra, đối tượng XmlReader còn
cung cấp phương thức ReadOuterXml() cho phép đọc toàn bộ dữ liệu XML thực sự
được trả về. Đoạn mã sau minh họa cách đọc dữ liệu XML từ XmlReader.
myXmlReader.Read();
while (!myXmlReader.EOF)
{
Console.WriteLine(myXmlReader.ReadOuterXml());
}
Đoạn chương trình sau minh họa các sử dụng phương thức ExecuteXmlReader.
Chương trình 4.6: Đọc dữ liệu ở dạng XML dùng phương thức ExecuteXmlReader
/*
Trang 134
Giáo trình lập trình cơ sở dữ liệu
using System;
using System.Data;
using System.Data.SqlClient;
using System.Xml;
class ExecuteXmlReader
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID " +
"FOR XML AUTO";
mySqlConnection.Open();
// read the rows from the XmlReader object using the Read()
// method
myXmlReader.Read();
while (!myXmlReader.EOF)
{
Console.WriteLine(myXmlReader.ReadOuterXml());
}
myXmlReader.Close();
mySqlConnection.Close();
}
}
Trang 135
Giáo trình lập trình cơ sở dữ liệu
4.4. Thực thi các lệnh làm thay đổi thông tin trong cơ sở dữ liệu
Đối tượng Command cung cấp phương thức ExecuteNonQuery cho phép thực thi
các lệnh không yêu cầu dữ liệu trả về từ cơ sở dữ liệu. Phần này trình bày cách sử dụng
phương thức ExecuteNonQuery để thực thi các truy vấn để thêm (INSERT), xóa
(DELETE) hay cập nhật (UPDATE) dữ liệu trong các bảng.
Bạn cũng có thể dùng phương thức ExecuteNonQuery để gọi một thủ tục (Stored
Procedure) hay thực thi một lệnh DDL (Data Definition Language) như tạo bảng
(CREATE TABLE), tạo chỉ mục (CREATE INDEX)…
METHOD RETURN TYPE DESCRIPTION
ExecuteNonQuery() int Được dùng để thực thi các lệnh SQL không trả
về tập kết quả như INSERT, UPDATE, DELETE, các
lệnh DDL, lời gọi thủ tục không trả về dữ
liệu. Giá trị trả về của hàm này chính là số
dòng bị ảnh hưởng khi thực thi truy vấn.
Bảng 4.8. Phương thức ExecuteNonQuery.
Tiếp theo, gán chuỗi lệnh INSERT cho thuộc tính CommandText của đối tượng
Command. Ví dụ sau gán một lệnh INSERT cho thuộc tính CommandText của đối
tượng mySqlCommand để thêm một dòng mới vào bảng Customers.
mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName" +
") VALUES (" +
" 'J2COM', 'Jason Price Corporation'" +
")";
Cuối cùng, gọi phương thức ExecuteNonQuery để thực thi lệnh INSERT.
int numberOfRows = mySqlCommand.ExecuteNonQuery();
Phương thức ExecuteNonQuery trả về một giá trị nguyên (kiểu int) cho biết số dòng
bị ảnh hưởng khi lệnh được thực thi. Trong ví dụ này, giá trị trả về chính là số dòng
được thêm vào bảng Customers. Chỉ có một hàng được thêm vào bởi lệnh INSERT, do
đó kết quả trả về là 1.
Trang 136
Giáo trình lập trình cơ sở dữ liệu
numberOfRows = mySqlCommand.ExecuteNonQuery();
Phương thức ExecuteNonQuery trả về số dòng bị thay đổi bởi lệnh UPDATE. Trong
trường hợp này, giá trị trả về là 1 vì chỉ có 1 dòng bị thay đổi.
numberOfRows = mySqlCommand.ExecuteNonQuery();
Kết quả trả về của phương thức ExecuteNonQuery là 1 vì chỉ có một dòng bị xóa bởi
lệnh DELETE.
Đoạn chương trình sau minh họa cách sử dụng phương thức ExecuteNonQuery để
thực thi các lệnh INSERT, UPDATE và DELETE. Chương trình này có một thủ tục
DisplayRow() để lấy và hiển thị chi tiết một hàng trong bảng Customers. Hàm này được
dùng trong chương trình để cho biết kết quả của lệnh INSERT và UPDATE.
Chương trình 4.7: Thực thi lệnh INSERT, UPDATE, DELETE
/*
ExecuteInsertUpdateDelete.cs illustrates how to use the
ExecuteNonQuery() method to run INSERT, UPDATE,
and DELETE statements
*/
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteInsertUpdateDelete
{
public static void DisplayRow(
SqlCommand mySqlCommand, string CustomerID)
{
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = '" + CustomerID + "'";
SqlDataReader mySqlDataReader =
Trang 137
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.ExecuteReader();
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"CustomerID\"] = " +
mySqlDataReader["CustomerID"]);
Console.WriteLine("mySqlDataReader[\"CompanyName\"] = " +
mySqlDataReader["CompanyName"]);
}
mySqlDataReader.Close();
}
mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName" +
") VALUES (" +
" 'J2COM', 'Jason Price Corporation'" +
")";
mySqlConnection.Open();
DisplayRow(mySqlCommand, "J2COM");
mySqlCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'New Company' " +
"WHERE CustomerID = 'J2COM'";
numberOfRows = mySqlCommand.ExecuteNonQuery();
DisplayRow(mySqlCommand, "J2COM");
Trang 138
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = 'J2COM'";
numberOfRows = mySqlCommand.ExecuteNonQuery();
mySqlConnection.Close();
}
}
Tiếp theo, gán lệnh DDL muốn thực thi cho thuộc tính CommandText của đối tượng
Command.
Ví dụ 1:
Trong ví dụ này, ta gán một lệnh CREATE TABLE cho thuộc tính CommandText
của đối tượng mySqlCommand để tạo ra một bảng mới tên MyPersons có 4 cột:
PersonID, FirstName, LastName và DateOfBirth.
mySqlCommand.CommandText =
"CREATE TABLE MyPersons (" +
" PersonID int CONSTRAINT PK_Persons PRIMARY KEY," +
" FirstName nvarchar(15) NOT NULL," +
" LastName nvarchar(15) NOT NULL," +
" DateOfBirth datetime" +
Trang 139
Giáo trình lập trình cơ sở dữ liệu
")";
Gọi phương thức ExecuteNonQuery để thực thi câu lệnh CREATE TABLE
int result = mySqlCommand.ExecuteNonQuery();
Lệnh CREATE TABLE không ảnh hưởng đến bất kỳ dòng nào vì thế, kết quả trả về
của hàm ExecuteNonQuery là -1.
result = mySqlCommand.ExecuteNonQuery();
Kết quả trả về của phương thức ExecuteNonQuery cũng là -1 vì lệnh ALTER TABLE
không ảnh hưởng đến bất cứ dòng nào.
Đoạn chương trình hoàn chỉnh sau minh họa cách thực thi các lệnh DDL bởi phương
thức ExecuteNonQuery.
Chương trình 4.8: Thực thi lệnh DDL dùng phương thức ExecuteNonQuery
/*
ExecuteDDL.cs illustrates how to use the ExecuteNonQuery()
method to run DDL statements
*/
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteDDL
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
Trang 140
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"CREATE TABLE MyPersons (" +
" PersonID int CONSTRAINT PK_Persons PRIMARY KEY," +
" FirstName nvarchar(15) NOT NULL," +
" LastName nvarchar(15) NOT NULL," +
" DateOfBirth datetime" +
")";
mySqlConnection.Open();
mySqlCommand.CommandText =
"ALTER TABLE MyPersons " +
"ADD EmployerID nchar(5) CONSTRAINT FK_Persons_Customers " +
"REFERENCES Customers(CustomerID)";
result = mySqlCommand.ExecuteNonQuery();
result = mySqlCommand.ExecuteNonQuery();
mySqlConnection.Close();
}
}
Trang 141
Giáo trình lập trình cơ sở dữ liệu
Để thực thi một giao dịch dùng SqlConnection, thực hiện theo các bước sau:
- B1. Tạo một đối tượng SqlTransaction và bắt đầu giao dịch bằng cách gọi
phương thức BeginTransaction của đối tượng SqlConnection.
- B2. Tạo đối tượng SqlCommand để thực thi truy vấn SQL.
- B3. Gán đối tượng SqlTransaction đã tạo ở bước 1 cho thuộc tính Transaction
của đối tượng SqlCommand.
- B4. Gán câu lệnh INSERT thứ nhất cho thuộc tính CommandText của đối tượng
SqlCommand. Lệnh INSERT này thêm một dòng mới vào bảng Customers.
- B5. Thực thi truy vấn bằng cách gọi hàm ExecuteNonQuery của đối tượng
SqlCommand. Ta dùng phương thức này vì lệnh INSERT không trả về tập dữ
liệu.
- B6. Gán câu lệnh INSERT thứ hai cho thuộc tính CommandText của đối tượng
SqlCommand. Lệnh này thêm một dòng mới vào bảng Orders.
- B7. Thực thi lệnh truy vấn bằng cách gọi hàm ExecuteNonQuery của đối tượng
SqlCommand.
- B8. Thực thi (commit) giao dịch bằng cách gọi phương thức Commit của đối
tượng SqlTransaction. Việc này làm cho cả hai dòng mới được thêm vào cơ sở
dữ liệu bởi lệnh INSERT.
Trang 142
Giáo trình lập trình cơ sở dữ liệu
Chương trình minh họa các bước thực hiện giao dịch trong ví dụ trên
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteTransaction
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
mySqlConnection.Open();
SqlTransaction mySqlTransaction =
mySqlConnection.BeginTransaction();
mySqlCommand.Transaction = mySqlTransaction;
mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName" +
") VALUES (" +
" 'J3COM', 'Jason Price Corporation'" +
")";
mySqlCommand.ExecuteNonQuery();
mySqlCommand.CommandText =
"INSERT INTO Orders (" +
Trang 143
Giáo trình lập trình cơ sở dữ liệu
" CustomerID" +
") VALUES (" +
" 'J3COM'" +
")";
mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Committing transaction");
mySqlTransaction.Commit();
mySqlConnection.Close();
}
}
Nếu muốn hủy bỏ các lệnh đã tạo trong giao dịch, ta gọi phương thức Rollback thay
vì Commit. Theo mặc định, các giao dịch sẽ bị gỡ bỏ (roll back). Vì thế, bạn luôn phải
gọi phương thức Commit hoặc Rollback để chỉ rõ bạn muốn xác nhận hay hủy bỏ các
giao dịch.
Nếu muốn chạy chương trình nhiều lần, cần phải xóa các dòng đã thêm vào trong
bảng Customers và Orders sử dụng lệnh DELETE.
Giá trị của các cột CustomerID, CompanyName tương ứng đều được gán cứng là
J3COM và Jason Price Corporation. Nếu phải thực thi nhiều lệnh với giá trị các cột
được gắn cứng sẽ gây nhiều rắc rối và không hiệu quả. Để giải quyết vấn đề này,
ADO.Net cho phép truyền giá trị vào lệnh bằng cách dùng các tham số. Các tham số
cho phép bạn truyền các giá trị khác nhau vào cùng một lệnh khi chạy chương trình.
Để thực thi một lệnh có chứa tham số, thực hiện các bước sau:
Trang 144
Giáo trình lập trình cơ sở dữ liệu
- B1. Tạo một đối tượng Command chứa lệnh SQL với các điểm đánh dấu tham số
(parameter placeholder). Điểm đánh dấu tham số chỉ ra vị trí mà một tham số
được cung cấp.
- B2. Thêm các tham số vào đối tượng Command.
- B3. Gán giá trị cho các tham số.
- B4. Thực thi lệnh.
Điểm đánh dấu có thể được dùng thay cho giá trị của một cột bất kỳ trong các lệnh
SELECT, INSERT, UPDATE hay DELETE. Sau đây là một ví dụ về các lệnh
SELECT, UPDATE và DELETE có chứa điểm đánh dấu.
SELECT *
FROM Customers
WHERE CustomerID = @CustomerID
UPDATE Customers
SET CompanyName = @CompanyName
WHERE CustomerID = @CustomerID
Để thực thi các lệnh dạng này, trước hết, ta tạo một đối tượng SqlCommand và gán
lệnh truy vấn cho thuộc tính CommandText của SqlCommand.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName, ContactName" +
") VALUES (" +
" @CustomerID, @CompanyName, @ContactName" +
")";
Lệnh INSERT được dùng để thêm một hàng vào bảng Customers. Giá trị của các cột
trên dòng này sẽ được truyền vào lệnh qua các tham số. Trước khi thực thi lệnh, cần
phải thêm các tham số thực sự vào đối tượng SqlCommand.
Để thêm các tham số thực vào đối tượng Command, ta dùng phương thức Add.
Phương thức này có nhiều dạng. Phần này trình bày một dạng của phương thức Add với
3 tham số.
Tên tham số hay tên dùng để ký hiệu điểm đánh dấu tham số trong lệnh SQL.
Chẳng hạn, @CustomerID là tên tham số đầu tiên trong lệnh INSERT ở ví dụ
trên.
Kiểu dữ liệu của cột trong cơ sở dữ liệu. với SQL Server, các kiểu này được định
nghĩa bởi kiểu liệt kê System.Data.SqlDbType.
MEMBER DESCRIPTION
BigInt Số nguyên dài có dấu 64-bit có giá trị từ -263 (-
9,223,372,036,854,775,808) đến 263-1
(9,223,372,036,854,775,807).
Binary Một mảng các byte với chiều dài tối đa 8,000 bytes.
Bit Giá trị số không dấu, chỉ nhận giá trị 0, 1 hoặc giá trị
null.
Char Chuỗi ký tự không phải Unicode, kích thước tối đa 8000 ký
tự.
DateTime Kiểu ngày giờ, có giá trị từ 12:00:00 AM January 1, 1753
đến 11:59:59 PM December 31, 9999. Độ chính xác 3.33 mili
giây.
Decimal Số thập phân có giá trị từ -1038 + 1 đến 1038 - 1.
Float Số có dấu chấm động 64-bit có giá trị từ -
1.79769313486232E308 đến 1.79769313486232E308.
Image Một mảng các byte với chiều dài tối đa 231 - 1
(2,147,483,647) bytes.
Int Số nguyên có dấu 32-bit có giá trị từ -231 (-2,147,483,648)
đến 231 - 1 (2,147,483,647).
Money Kiểu tiền tệ, có giá trị từ -922,337,203,685,477.5808 đến
922,337,203,685,477.5807. Độ chính xác 1/10.000.
NChar Chuỗi ký tự Unicode chiều dài cố định, tối đa 4.000 ký tự.
Ntext Kiểu chuỗi ký tự Unicode chiều dài tối đa 230 - 1
(1,073,741,823) ký tự.
NVarChar Chuỗi ký tự Unicode với chiều dài thay đổi, tối đa 4.000
ký tự.
Real Số dấu chấm động 32-bit có giá trị từ -3.402823E38 đến
3.402823E38, bảy chữ số thập phân có ý nghĩa.
SmallDateTime Kiểu ngày giờ có giá trị từ 12:00:00 AMJanuary 1, 1900 đến
11:59:59 PM June 6, 2079. Độ chính xác 1 phút.
SmallInt Số nguyên có dấu 16-bit có giá trị từ -215 (-32,768) đến 215
- 1 (32,767).
SmallMoney Kiểu tiền tệ, có giá trị từ -214,748.3648 đến
214,748.3647. Độ chính xác 1/10,000.
Text Chuỗi ký tự không phải Unicode với chiều dài tối đa 231 - 1
(2,147,483,647) ký tự.
Timestamp Kiểu ngày giờ với định dạng yyyymmddhhmmss.
TinyInt Kiểu số nguyên không dấu 8-bit có giá trị từ 0 đến 28 - 1
(255).
UniqueIdentifier Kiểu số nguyên 128-bit (16 bytes) biểu diễn một giá trị
định danh duy nhất trên tất cả các máy tính và các mạng.
Trang 146
Giáo trình lập trình cơ sở dữ liệu
MEMBER DESCRIPTION
VarBinary Một mảng bytes với chiều dài tối đa 8,000 bytes.
VarChar Chuỗi ký tự không phải Unicode với kích thước tối đa 4000
bytes.
Variant Kiểu dữ liệu có thể chứa số, chuỗi, bytes hay ngày tháng.
Bảng 4.9. Các hằng số trong SqlDbType
Kích thước tối đa của giá trị tham số. Tham số này chỉ được dùng cho các kiểu
dữ liệu có chiều dài thay đổi như char, varchar hay nvarchar.
Trong bước 1, thuộc tính CommandText của SqlCommand được gán lệnh với ba
điểm đánh dấu tham số
mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName, ContactName" +
") VALUES (" +
" @CustomerID, @CompanyName, @ContactName" +
")";
Đoạn mã sau dùng phương thức Add để thêm ba tham số vào đối tượng SqlCommand
mySqlCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5);
mySqlCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 40);
mySqlCommand.Parameters.Add("@ContactName", SqlDbType.NVarChar, 30);
Phương thức Add được gọi qua thuộc tính Parameters của đối tượng SqlCommand.
Một đối tượng SqlCommand chứa thuộc tính Parameters có kiểu
SqlParameterCollection để lưu trữ nhiều tham số. Mỗi tham số được lưu trong một đối
tượng SqlParameter. Lớp SqlParameterCollection cung cấp phương thức Add để thêm
một đối tượng SqlParameter. Vì thế, để thêm một tham số vào SqlCommand, ta gọi
phương thức Add của thuộc tính Parameters.
Trong đoạn mã trên, tham số @CustomerID được định nghĩa là một chuỗi ký tự
Unicode kiệu NChar với chiều dài chuỗi là 5 (nghĩa là tham số này nhận giá trị là một
chuỗi có tối đa 5 ký tự). Tương tự, các tham số @CompanyName và @ContactName
được định nghĩa là một chuỗi ký tự Unicode kiểu NVarChar, chiều dài tương ứng là 40
và 30 ký tự.
Trang 147
Giáo trình lập trình cơ sở dữ liệu
Để đơn giản, ta cũng có thể vừa thêm một tham số, vừa gán giá trị cho nó như ví dụ sau:
mySqlCommand.Parameters.Add("@CustomerID",
SqlDbType.NChar, 5).Value = "J4COM";
Đối với những cột có thể nhận giá trị null, ta có thể gán giá trị null bằng cách đặt
thuộc tính IsNullable của tham số là true hoặc gán cho thuộc tính Value một giá trị đặc
biệt DbNull.Value. Giá trị null thể hiện cho một giá trị chưa biết. Trong trường hợp
này, giá trị của tham số sẽ tự động được chuyển sang giá trị NULL trong cơ sở dữ liệu
khi thay thế vào trong lệnh truy vấn
mySqlCommand.Parameters["@ContactName"].IsNullable = true;
hoặc
mySqlCommand.Parameters["@ContactName"].Value = DBNull.Value;
using System;
using System.Data;
using System.Data.SqlClient;
class UsingParameters
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
mySqlConnection.Open();
Trang 148
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName, ContactName" +
") VALUES (" +
" @CustomerID, @CompanyName, @ContactName" +
")";
mySqlCommand.Parameters["@CustomerID"].Value = "J4COM";
mySqlCommand.Parameters["@CompanyName"].Value = "J4 Company";
mySqlCommand.Parameters["@ContactName"].IsNullable = true;
mySqlCommand.Parameters["@ContactName"].Value = DBNull.Value;
mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Successfully added row to Customers table");
mySqlConnection.Close();
}
}
Trang 149
Giáo trình lập trình cơ sở dữ liệu
- B1. Tạo một đối tượng Command và gán giá trị cho thuộc tính CommandText là
lệnh EXECUTE theo sau bởi tên thủ tục cần gọi.
- B2. Thêm các tham số cần thiết cho lời gọi thủ tục vào đối tượng Command.
Những tham số nào lưu giá trị trả về phải được thiết lập thuộc tính Direction là
Parameter-Direction.Output. Những tham số dạng này được định nghĩa bởi từ
khóa OUTPUT trong lời gọi thủ tục hoặc được trả về bởi lệnh RETURN trong
thủ tục.
- B3. Thực thi lệnh bằng cách gọi phương thức ExecuteNonQuery của đối tượng
Command.
- B4. Đọc giá trị từ các tham số.
Các ví dụ sau minh họa cách thực thi một thủ tục dùng ADO.NET và đọc kết quả từ các
tham số.
Trang 150
Giáo trình lập trình cơ sở dữ liệu
Bước 1: Tạo đối tượng Command và thiết lập lệnh truy vấn dùng EXECUTE
Tạo một đối tượng Command, gán giá trị cho thuộc tính CommandText của nó là
lệnh truy vấn EXECUTE theo sau bởi lời gọi thủ tục và danh sách các tên tham số.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"EXECUTE AddProduct @MyProductID OUTPUT, @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";
Trong đoạn mã trên, tham số @MyProductID theo sau bởi từ khóa OUTPUT cho
biết đây là tham số có thể lưu lại kết quả sau khi gọi thủ tục. Các tham số khác chứa các
giá trị để thêm một dòng mới bởi lệnh INSERT.
Bước 2: Thêm các tham số cần thiết vào đối tượng Command
Ở bước này, ta tiến hành thêm các tham số vào đối tượng Command. Lưu ý: phải đặt
giá trị ParameterDirection.Output cho thuộc tính Direction của các tham số ra.
Chẳng hạn, thủ tục AddProduct có một tham số ra để lưu ProductID của dòng mới.
Do đó, đối tượng Command yêu cầu một tham số với Direction là Output.
mySqlCommand.Parameters.Add("@MyProductID", SqlDbType.Int);
mySqlCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
Các tham số khác được truyền vào lời gọi thủ tục như sau:
mySqlCommand.Parameters.Add("@MyProductID", SqlDbType.Int);
mySqlCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
mySqlCommand.Parameters.Add(
"@MyProductName", SqlDbType.NVarChar, 40).Value = "Widget";
mySqlCommand.Parameters.Add(
"@MySupplierID", SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add(
"@MyCategoryID", SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add(
"@MyQuantityPerUnit", SqlDbType.NVarChar, 20).Value = "1 per box";
mySqlCommand.Parameters.Add(
"@MyUnitPrice", SqlDbType.Money).Value = 5.99;
mySqlCommand.Parameters.Add(
Trang 151
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.Parameters.Add(
"@MyUnitsOnOrder", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyReorderLevel", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyDiscontinued", SqlDbType.Bit).Value = 1;
Lưu ý: kiểu dữ liệu SqlDbType của tham số phải tương ứng với kiểu của tham số đã
khai báo khi tạo thủ tục.
Đoạn chương trình sau minh họa các bước để thực thi một lời gọi thủ tục
Chương trình 4.11: Thực thi một thủ tục (Stored Procedure) SQL Server
/*
ExecuteAddProduct.cs illustrates how to call the SQL Server
AddProduct() stored procedure
*/
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteAddProduct
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
mySqlConnection.Open();
Trang 152
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"EXECUTE AddProduct @MyProductID OUTPUT, @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";
mySqlCommand.Parameters.Add("@MyProductID", SqlDbType.Int);
mySqlCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
mySqlCommand.Parameters.Add(
"@MyProductName", SqlDbType.NVarChar, 40).Value = "Widget";
mySqlCommand.Parameters.Add("@MySupplierID",
SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add("@MyCategoryID",
SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add("@MyQuantityPerUnit",
SqlDbType.NVarChar, 20).Value = "1 per box";
mySqlCommand.Parameters.Add(
"@MyUnitPrice", SqlDbType.Money).Value = 5.99;
mySqlCommand.Parameters.Add(
"@MyUnitsInStock", SqlDbType.SmallInt).Value = 10;
mySqlCommand.Parameters.Add(
"@MyUnitsOnOrder", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyReorderLevel", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add("@MyDiscontinued",
SqlDbType.Bit).Value = 1;
mySqlCommand.ExecuteNonQuery();
mySqlConnection.Close();
}
}
Trang 153
Giáo trình lập trình cơ sở dữ liệu
RETURN @MyProductID
Lệnh RETURN được đặt cưới thủ tục để trả về ProductID của dòng mới được thêm
vào bảng Products. Vì thủ tục này không trả về một tập các dòng nên ta cũng áp dụng 4
bước đã trình bày ở phần trước để thực thi lời gọi thủ tục bằng ADO.NET. Điểm khác
Trang 154
Giáo trình lập trình cơ sở dữ liệu
biệt duy nhất cấu trúc của lệnh EXECUTE được gán cho thuộc tính CommandText
trong bước 1. Lệnh EXECUTE trong trường hợp này được viết như sau:
mySqlCommand.CommandText =
"EXECUTE @MyProductID = AddProduct2 @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";
Chú ý sự thay đổi vị trí của tham số @MyProductID. Nó được chuyển về nằm ngay
sau từ khóa EXECUTE, theo sau bởi dấu bằng “=” và tên của thủ tục. Sự thay đổi này là
cần thiết vì thủ tục AddProduct2 dùng câu lệnh RETURN để trả về giá trị ProductID và
gắn cho tham số @MyProductID.
Ví dụ: Đoạn chương trình sau minh họa cách lấy dữ liệu trả về bởi thủ tục có dùng lệnh
RETURN.
Chương trình 4.12: Gọi thủ tục (Stored Procedure) SQL Server
/*
ExecuteAddProduct2.cs illustrates how to call the SQL Server
AddProduct2() stored procedure
*/
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteAddProduct2
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlConnection.Open();
mySqlCommand.CommandText =
"EXECUTE @MyProductID = AddProduct2 @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";
mySqlCommand.Parameters.Add("@MyProductID", SqlDbType.Int);
mySqlCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
Trang 155
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.Parameters.Add(
"@MyProductName", SqlDbType.NVarChar, 40).Value = "Widget";
mySqlCommand.Parameters.Add(
"@MySupplierID", SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add(
"@MyCategoryID", SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add("@MyQuantityPerUnit",
SqlDbType.NVarChar, 20).Value = "1 per box";
mySqlCommand.Parameters.Add(
"@MyUnitPrice", SqlDbType.Money).Value = 5.99;
mySqlCommand.Parameters.Add(
"@MyUnitsInStock", SqlDbType.SmallInt).Value = 10;
mySqlCommand.Parameters.Add(
"@MyUnitsOnOrder", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyReorderLevel", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyDiscontinued", SqlDbType.Bit).Value = 1;
mySqlCommand.ExecuteNonQuery();
mySqlConnection.Close();
}
}
Trang 156
Giáo trình lập trình cơ sở dữ liệu
- B4. Đọc các dòng trong tập kết quả bằng phương thức Read của đối tượng
DataReader
- B5. Đóng đối tượng DataReader. Việc này phải được thực hiện trước khi đọc giá
trị các tham số OUTPUT.
- B6. Đọc giá trị của các tham số nhận giá trị trả về.
Ví dụ:
Đoạn mã sau định nghĩa một thủ tục có tên AddProduct3 trả về một tập dữ liệu và
một giá trị lưu trong một tham số OUTPUT bởi lệnh RETURN. Thủ tục AddProduct3
cũng tương tự như AddProduct2 ngoại trừ việc nó trả về một tập dữ liệu bởi câu lệnh
SELECT. Lệnh này chứa các cột ProductName và UnitPrice của dòng mới được thêm
vào bảng Products. Ngoài ra, thủ tục còn sử dụng lệnh RETURN để trả về giá trị
ProductID của dòng mới.
/*
AddProduct3.sql creates a procedure that adds a row to the
Products table using values passed as parameters to the
procedure. The procedure returns the ProductID of the new row
using a RETURN statement and returns a result set containing
the new row
*/
CREATE PROCEDURE AddProduct3
@MyProductName nvarchar(40),
@MySupplierID int,
@MyCategoryID int,
@MyQuantityPerUnit nvarchar(20),
@MyUnitPrice money,
@MyUnitsInStock smallint,
@MyUnitsOnOrder smallint,
@MyReorderLevel smallint,
@MyDiscontinued bit
AS
Trang 157
Giáo trình lập trình cơ sở dữ liệu
-- return @MyProductID
RETURN @MyProductID
Đoạn chương trình sau minh họa cách lấy dữ liệu trả về từ thủ tục và từ tham số
OUTPUT của thủ tục.
using System;
using System.Data;
using System.Data.SqlClient;
class ExecuteAddProduct3
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
mySqlConnection.Open();
mySqlCommand.CommandText =
"EXECUTE @MyProductID = AddProduct3 @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";
mySqlCommand.Parameters.Add("@MyProductID", SqlDbType.Int);
mySqlCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
mySqlCommand.Parameters.Add(
"@MyProductName", SqlDbType.NVarChar, 40).Value = "Widget";
mySqlCommand.Parameters.Add(
Trang 158
Giáo trình lập trình cơ sở dữ liệu
"@MySupplierID", SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add(
"@MyCategoryID", SqlDbType.Int).Value = 1;
mySqlCommand.Parameters.Add( "@MyQuantityPerUnit",
SqlDbType.NVarChar, 20).Value = "1 per box";
mySqlCommand.Parameters.Add(
"@MyUnitPrice", SqlDbType.Money).Value = 5.99;
mySqlCommand.Parameters.Add(
"@MyUnitsInStock", SqlDbType.SmallInt).Value = 10;
mySqlCommand.Parameters.Add(
"@MyUnitsOnOrder", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyReorderLevel", SqlDbType.SmallInt).Value = 5;
mySqlCommand.Parameters.Add(
"@MyDiscontinued", SqlDbType.Bit).Value = 1;
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"ProductName\"] = " +
mySqlDataReader["ProductName"]);
Console.WriteLine("mySqlDataReader[\"UnitPrice\"] = " +
mySqlDataReader["UnitPrice"]);
}
mySqlDataReader.Close();
mySqlConnection.Close();
}
}
Trang 159
Giáo trình lập trình cơ sở dữ liệu
Trang 160
Giáo trình lập trình cơ sở dữ liệu
Chẳng hạn, trong ví dụ này, ta chọn đối tượng sqlConnection1 có sẵn trong ô chọn
của thuộc tính Connection của đối tượng sqlCommand1 như hình 4.2.
Bạn cũng có thể dùng trình Query Builder để tạo một lệnh SQL bằng cách nhấp
chọn nút “…” bên phải thuộc tính CommandText, gán tham số cho một lệnh bằng cách
nhấp chọn nút “…” bên phải thuộc tính Parameters.
Trong ví dụ này, ta dùng Query Builder để gán cho thuộc tính CommandText của
đối tượng sqlCommand1 là một lệnh SELECT, lấy về các cột CustomerID,
CompanyName và ContactName từ bảng Customers.
Hình 4.2. Cấu hình kết nối cho đối tượng Command
Trong hộp thoại Add Table, chọn bảng Customers và nhấn nút Add để thêm bảng
Customers vào truy vấn (hình 4.3). Tạo truy vấn dùng Query Builder bằng cách chọn
các cột CustomerID, CompanyName và ContactName từ bảng Customers trong cửa sổ
Query Builder (hình 4.4). Nhấn nút Execute Query để kiểm tra trước kết quả truy vấn.
Trang 161
Giáo trình lập trình cơ sở dữ liệu
Trang 162
Giáo trình lập trình cơ sở dữ liệu
1. Đối tượng Command dùng để làm gì? Nêu các thuộc tính, phương thức của đối
tượng Command và ý nghĩa của chúng.
2. Trình bày các bước để thực thi một lệnh truy vấn bởi đối tượng Command của
ADO.Net.
3. Trình bày cách thực thi một lệnh truy vấn SQL có chứa tham số, có chứa tham số trả
về.
4. Trình bày cách thực thi một thủ tục lưu trữ SQL Server: không có tham số, có tham
số, có tham số trả về.
5. Trình bày các thực thi một lệnh DDL để tạo một khung nhìn cho phép xem danh
sách các khoa và môn học thuộc khoa đó (lưu vào cơ sở dữ liệu trong bài tập 7
chương 1).
6. Viết các phương thức để thực thi các lệnh truy vấn, các thủ tục đã tạo trong bài tập 8
chương 1.
Trang 163
Giáo trình lập trình cơ sở dữ liệu
5. CHƯƠNG 5
LỚP DATAREADER
Lớp DataReader được dùng để đọc kết quả trả về từ cơ sở dữ liệu bởi đối tượng
Command. Việc đọc các dòng trong tập kết quả dùng đối tượng DataReader thường
nhanh hơn đọc dữ liệu từ một DataSet. Các đối tượng DataReader đều thuộc lớp kết nối,
có 3 lớp DataReader: SqlDataReader, OleDbDataReader và OdbcDataReader.
Các đối tượng DataReader chỉ đọc các dòng trong tập kết quả theo một chiều từ đầu
đến cuối. Nó có thể dùng thay cho đối tượng DataSet để đọc dữ liệu nhưng không thể
làm thay đổi các dòng trong cơ sở dữ liệu. Đối tượng DataSet cho phép lưu một bản sao
của các dòng trong cơ sở dữ liệu. Vì thế, ta có thể xử lý dữ liệu trên bảng sao mà không
cần giữ kết nối.
Trang 165
Giáo trình lập trình cơ sở dữ liệu
Trang 166
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice, " +
"UnitsInStock, Discontinued " +
"FROM Products " +
"ORDER BY ProductID";
mySqlConnection.Open();
SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader();
Trong ví dụ này, đối tượng SqlDataReader trả về bởi phương thức ExecuteReader có
tên là productsSqlDataReader.
Mặc dù cách dùng chỉ số cột làm cho chương trình chạy nhanh hơn nhưng lại làm
cho mã khó hiểu, không linh hoạt vì phải xác định một cột có chỉ số là bao nhiêu và
ngược lại. Nếu vị trí của các cột trong lệnh SELECT bị thay đổi, ta phải thay đổi chỉ số
Trang 167
Giáo trình lập trình cơ sở dữ liệu
cột đã gắn cứng trong mã của chương trình. Mặt khác, việc dùng chỉ số cột cũng làm
cho lập trình viên khó đọc hiểu mã hơn.
Một giải pháp để khắc phục vấn đề này là dùng phương thức GetOrdinal của đối
tượng DataReader. Phương thức này trả về vị trí của cột có tên được truyền qua tham số
của hàm. Vị trí này còn được gọi là thứ tự của cột (ordinal). Ta dùng kết quả trả về của
hàm GetOrdinal làm thứ tự cột để lấy giá trị cột đó từ DataReader.
Đây là cách tốt nhất để lấy dữ liệu từ DataReader, cả về tính mềm dẻo, linh động và
hiệu quả cao.
Ví dụ:
Đoạn mã sau sử dụng phương thức GetOrdinal để lấy vị trí các cột có trong câu lệnh
SELECT trên. Sau đó sử dụng chỉ số cột để lấy giá trị trên các cột này từ đối tượng
DataReader.
int productIDColPos =
productsSqlDataReader.GetOrdinal("ProductID");
int productNameColPos =
productsSqlDataReader.GetOrdinal("ProductName");
int unitPriceColPos =
productsSqlDataReader.GetOrdinal("UnitPrice");
int discontinuedColPos =
productsSqlDataReader.GetOrdinal("Discontinued");
while (productsSqlDataReader.Read())
{
Console.WriteLine(productsSqlDataReader[productIDColPos]);
Console.WriteLine(productsSqlDataReader[productNameColPos]);
Console.WriteLine(productsSqlDataReader[unitPriceColPos]);
Console.WriteLine(productsSqlDataReader[discontinuedColPos]);
}
Đối tượng DataReader cần phải được đóng sau khi hoàn tất việc đọc dữ liệu bằng
cách gọi phương thức Close. Lý do cần phải đóng là vì đối tượng DataReader sử dụng
kết hợp với đối tượng Connection. Nếu không được đóng, kết nối vẫn được mở và dành
riêng cho DataReader trong khi không còn lệnh nào được thực hiện. Một khi đã đóng
DataReader, kết nối thể thể dùng để thực thi các lệnh truy vấn khác.
productsSqlDataReader.Close();
Sau đây là chương trình hoàn chỉnh minh họa cách đọc dữ liệu trả về từ cơ sở dữ liệu
qua đối tượng DataReader.
Trang 168
Giáo trình lập trình cơ sở dữ liệu
using System;
using System.Data;
using System.Data.SqlClient;
class UsingColumnOrdinals
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice, " +
"UnitsInStock, Discontinued " +
"FROM Products " +
"ORDER BY ProductID";
mySqlConnection.Open();
SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader();
int productNameColPos =
productsSqlDataReader.GetOrdinal("ProductName");
int unitPriceColPos =
productsSqlDataReader.GetOrdinal("UnitPrice");
int unitsInStockColPos =
productsSqlDataReader.GetOrdinal("UnitsInStock");
int discontinuedColPos =
productsSqlDataReader.GetOrdinal("Discontinued");
Console.WriteLine("ProductName = " +
productsSqlDataReader[productNameColPos]);
Console.WriteLine("UnitPrice = " +
productsSqlDataReader[unitPriceColPos]);
Console.WriteLine("UnitsInStock = " +
productsSqlDataReader[unitsInStockColPos]);
Trang 169
Giáo trình lập trình cơ sở dữ liệu
Console.WriteLine("Discontinued = " +
productsSqlDataReader[discontinuedColPos]);
}
ProductID = 2
ProductName = Chang
UnitPrice = 19
UnitsInStock = 17
Discontinued = False
ProductID = 3
ProductName = Aniseed Syrup
UnitPrice = 10
UnitsInStock = 13
Discontinued = False
ProductID = 4
ProductName = Chef Anton's Cajun Seasoning
UnitPrice = 22
UnitsInStock = 53
Discontinued = False
ProductID = 5
ProductName = Chef Anton's Gumbo Mix
UnitPrice = 21.35
UnitsInStock = 0
Discontinued = True
Trang 170
Giáo trình lập trình cơ sở dữ liệu
object unitsInStock =
productsSqlDataReader[unitsInStockColPos];
object discontinued =
productsSqlDataReader[discontinuedColPos];
Đoạn mã này có cùng kết quả như trong ví dụ ở mục trước nhưng thay vì xuất trực
tiếp giá trị của các cột bởi phương thức Console.WriteLine, ta lưu các giá trị này vào các
đối tượng có kiểu System.Object. Sau đó, các đối tượng này được chuyển sang kiểu
chuỗi và xuất ra màn hình bởi phương thức Console.WriteLine.
Cách này thực hiện tốt nếu chỉ muốn xuất các giá trị ra màn hình. Trong trường hợp
cần thực hiện tính toán trên các giá trị lấy được, chúng phải được ép sang các kiểu cụ
thể. Chẳng hạn, để ép đối tượng unitPrice sang kiểu decimal và nhân với 1.2, ta viết:
decimal newUnitPrice = (decimal)unitPrice * 1.2m;
Ký hiệu m nằm sau số 1.2 để cho biết đó là số ở dạng decimal. Việc ép một đối
tượng sang kiểu nào đó là hoàn toàn có thể. Tuy nhiên, điều này không thực sự hiệu
quả. Nó cũng đi ngược với các ưu điểm chính của các ngôn ngữ lập trình bậc cao: sử
dụng kiểu rõ ràng. Kiểu dữ liệu rõ ràng (strongly typing) có nghĩa là bạn phải xác định
rõ kiểu dữ liệu của của biến hay đối tượng lúc khai báo. Ưu điểm của kiểu dữ liệu rõ
ràng là hạn chế được các lỗi trong lúc chạy chương trình do gán giá trị hay sử dụng sai
kiểu kiểu dữ liệu. Trình biên dịch sẽ kiểm tra mã chương trình để bảo đảm giá trị được
gán cho một biến có kiểu nào đó có hợp lệ hay không.
Một quy tắc hiệu quả là cố gắng khai báo tất cả các biến, các đối tượng với kiểu
thích hợp và chỉ ép kiểu khi không còn lựa chọn nào khác.
Trong ví dụ này, thay vì ép kiểu, bạn có thể dùng các phương thức Get* của đối
tượng DataReader có trả về kiểu thích hợp để lấy dữ liệu từ các cột. Dấu * trong Get*
có nghĩa là đối tượng DataReader có nhiều phương thức bắt đầu bởi Get và * dùng để
thay thế cho tên kiểu dữ liệu trả về của phương thức đó.
Chẳng hạn, phương thức GetInt32 trả về giá trị của một cột ở dạng số nguyên 32 bit.
Những phương thức dạng này đều chấp nhận tham số là tên hoặc thứ tự của cột muốn
lấy dữ liệu.
int productID =
productsSqlDataReader.GetInt32(productIDColPos);
Trang 171
Giáo trình lập trình cơ sở dữ liệu
Bảng 5.3 cho thấy các kiểu dữ liệu chuẩn của C# được tạo từ các kiểu cơ sở của
.NET và miền giá trị của mỗi kiểu. Bảng 5.4 liệt kê các kiểu dữ liệu trong cơ sở dữ liệu
SQL Server và các kiểu dữ liệu trong C# và các hàm Get* tương ứng cần gọi khi muốn
lấy giá trị của một cột.
C# TYPE .NET TYPE VALUES
bool Boolean Kiểu Logic Bool: nhận giá trị true hoặc false.
byte Byte Số nguyên không dấu, 8 bit, có giá trị từ 0 đến 28 -
1(255).
char Char Kiểu ký tự 16 bits (2 bytes).
DateTime DateTime Kiểu ngày giờ có giá trị từ 12:00:00 AM January 1,
0001 đến 11:59:59 PM December 31, 9999.
decimal Decimal Kiểu số thập phân với độ chính xác 29 chữ số.
Precision và Scale của nó xấp xỉ +/-1.0 *10-28 và +/-
7.9 *1028.
double Double A 64-bit floating-point number between approximately
+/-5 *10-324 and approximately +/-1.7 *10308 with 15 to
16 significant figures of precision.
float Single A 32-bit floating-point number between approximately
+/-1.5 *10-45 to approximately +/-3.4 *1038 with 7
significant figures of precision.
Guid Guid Kiểu số nguyên không dấu 128 bits (16 bytes) biểu
diễn định danh duy nhất trên tất cả các máy tính và
các mạng.
int Int32 Kiểu số nguyên có dấu 32 bits có giá trị từ -231 (-
2,147,483,648) đến 231 - 1 (2,147,483,647).
long Int64 Kiểu số nguyên có dấu 64 bits có giá trị từ -263 (-
9,223,372,036,854,775,808) đến 263 - 1
(9,223,372,036,854,775,807).
sbyte SByte Kiểu số nguyên có dấu 8 bit có giá trị từ -27 (-128)
đến 27 - 1 (127).
short Int16 Kiểu số nguyên có dấu 16 bits có giá trị từ -215 (-
32,768) đến 215 - 1 (32,767).
string String Kiểu chuỗi có chiều dài thay đổi, chứa các kí tự
Unicode 16 bits.
uint UInt32 Số nguyên không dấu 32-bit có giá trị từ 0 đến 232 -
1 (4,294,967,295).
ulong UInt64 Số nguyên không dấu 64-bit có giá trị từ 0 đến 264 –
1 (18,446,744,073,709,551,615).
ushort UInt16 Số nguyên không dấu 16-bit có giá trị từ 0 đến 216 -
1 (65,535).
Bảng 5.3. Các kiểu dữ liệu chuẩn của C# và .NET
Trang 172
Giáo trình lập trình cơ sở dữ liệu
Để hiểu rõ hơn, ta khảo sát một ví dụ dùng các phương thức Get* của đối tượng
DataReader để đọc giá trị trên các cột ProductID, ProductName, UnitPrice,
UnitsInStock và Discontinued của bảng Products.
Để biết nên dùng phương thức nào để đọc dữ liệu trên một cột, ta xác định kiểu dữ
liệu SQL Server của cột đó rồi dò trong bảng 5.4 để tìm ra hàm thích hợp. Bảng sau liệt
kê tên các cột, tên phương thức cần dùng và kiểu trả về khi đọc dữ liệu trên 5 cột của
bảng Products.
COLUMN NAME SQL SERVER COLUMN TYPE GET* METHOD C# RETURN TYPE
ProductID int GetInt32() int
ProductName nvarchar GetString() string
UnitPrice money GetDecimal() decimal
UnitsInStock smallint GetInt16() short
Discontinued bit GetBoolean() bool
Bảng 5.5. Products Columns and Get* Methods
Giả sử, ta có một đối tượng SqlDataReader tên là productsSqlDataReader dùng để
đọc giá trị trên 5 cột của bảng Products. Đoạn mã sau có một vòng lặp while sử dụng
các phương thức dạng Get* trả về kiểu dữ liệu chuẩn của C# để lấy giá trị các cột từ đối
tượng DataReader.
while (productsSqlDataReader.Read())
{
int productID =
productsSqlDataReader.GetInt32(productIDColPos);
Console.WriteLine("productID = " + productID);
string productName =
Trang 173
Giáo trình lập trình cơ sở dữ liệu
productsSqlDataReader.GetString(productNameColPos);
Console.WriteLine("productName = " + productName);
decimal unitPrice =
productsSqlDataReader.GetDecimal(unitPriceColPos);
Console.WriteLine("unitPrice = " + unitPrice);
short unitsInStock =
productsSqlDataReader.GetInt16(unitsInStockColPos);
Console.WriteLine("unitsInStock = " + unitsInStock);
bool discontinued =
productsSqlDataReader.GetBoolean(discontinuedColPos);
Console.WriteLine("discontinued = " + discontinued);
}
Trong ví dụ này, có 5 biến tương ứng với 5 kiểu dữ liệu khác nhau được tạo ra trong
vòng lặp, mỗi biến lưu giá trị trả về của một phương thức Get*. Chẳng hạn:
- Biến productID: lưu giá trị của cột ProductID. Vì cột ProductID có kiểu SQL
Server là int nên kiểu (chuẩn C#) của biến này cũng phải là int. Để lấy giá trị của
cột ProductID, ta gọi phương thức GetInt32.
- Biến productName: lưu giá trị của cột ProductName và có kiểu C# là string vì
kiểu SQL Server của cột ProductName là nvarchar. Để lấy giá trị của cột này, ta
dùng phương thức GetString.
Chương trình ví dụ sau minh họa cách đọc giá trị trên các cột dùng các phương thức
có dạng Get* của đối tượng DataReader.
Chương trình 5.2: Using the Get* Methods to Read Column Values
/*
StronglyTypedColumnValues.cs illustrates how to read
column values as C# types using the Get* methods
*/
Trang 174
Giáo trình lập trình cơ sở dữ liệu
using System;
using System.Data;
using System.Data.SqlClient;
class StronglyTypedColumnValues
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice, " +
"UnitsInStock, Discontinued " +
"FROM Products " +
"ORDER BY ProductID";
mySqlConnection.Open();
SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader();
int productIDColPos =
productsSqlDataReader.GetOrdinal("ProductID");
int productNameColPos =
productsSqlDataReader.GetOrdinal("ProductName");
int unitPriceColPos =
productsSqlDataReader.GetOrdinal("UnitPrice");
int unitsInStockColPos =
productsSqlDataReader.GetOrdinal("UnitsInStock");
int discontinuedColPos =
productsSqlDataReader.GetOrdinal("Discontinued");
Trang 175
Giáo trình lập trình cơ sở dữ liệu
string productName =
productsSqlDataReader.GetString(productNameColPos);
Console.WriteLine("productName = " + productName);
decimal unitPrice =
productsSqlDataReader.GetDecimal(unitPriceColPos);
Console.WriteLine("unitPrice = " + unitPrice);
short unitsInStock =
productsSqlDataReader.GetInt16(unitsInStockColPos);
Console.WriteLine("unitsInStock = " + unitsInStock);
bool discontinued =
productsSqlDataReader.GetBoolean(discontinuedColPos);
Console.WriteLine("discontinued = " + discontinued);
}
productsSqlDataReader.Close();
mySqlConnection.Close();
}
}
Trang 176
Giáo trình lập trình cơ sở dữ liệu
productID = 1
productName = Chai
unitPrice = 18
unitsInStock = 39
discontinued = False
productID = 2
productName = Chang
unitPrice = 19
unitsInStock = 17
discontinued = False
productID = 3
productName = Aniseed Syrup
unitPrice = 10
unitsInStock = 13
discontinued = False
productID = 4
productName = Chef Anton's Cajun Seasoning
unitPrice = 22
unitsInStock = 53
discontinued = False
productID = 5
productName = Chef Anton's Gumbo Mix
unitPrice = 21.35
unitsInStock = 0
discontinued = True
Trang 177
Giáo trình lập trình cơ sở dữ liệu
Nếu sử dụng SQL Server, bạn nên dùng các phương thức dạng GetSql* và kiểu dữ
liệu Sql* thay cho cách dùng Get* và kiểu dữ liệu chuẩn của C#.
Sql* TYPE VALUES
SqlBinary Kiểu dữ liệu nhị phân có chiều dài thay đổi.
SqlBoolean Kiểu số nguyên nhưng chỉ nhận một trong hai giá trị 1 hoặc 0.
SqlByte Số nguyên không dấu 8 bits có giá trị từ 0 đến 28 - 1 (255).
SqlDateTime Kiểu ngày giờ có giá trị từ 12:00:00 AM January 1, 1753 đến
11:59:59 PM December 31, 9999. Độ chính xác 3.33 mili giây.
SqlDecimal Kiểu số thập phân có giá trị từ -1038 + 1 đến 1038 - 1.
SqlDouble Số có dấu chấm động 64 bits có giá trị từ -1.79769313486232E308
đến 1.79769313486232E308 với 15 chữ số bên phải dấu chấm thập
phân.
SqlGuid Số nguyên 128 bits biểu diễn giá trị định danh duy nhất cho tất
cả các máy tính và mạng.
SqlInt16 Số nguyên có dấu 16 bits, có giá trị từ -215 (-32,768) đến 215 - 1
(32,767).
SqlInt32 Số nguyên có dấu 32 bits, có giá trị từ -231 (-2,147,483,648) đến
231 - 1 (2,147,483,647).
SqlInt64 Số nguyên có dấu 64 bits, có giá trị từ -263 (-
9,223,372,036,854,775,808) đến 263 - 1
(9,223,372,036,854,775,807).
SqlMoney Kiểu tiền tệ, có giá trị từ -922,337,203,685,477.5808 đến
922,337,203,685,477.5807. Độ chính xác 1/10,000.
SqlSingle Số có dấu chấm động 32 bits có giá trị từ -3.402823E38 đến
3.402823E38 với 7 chữ số bên phải dấu chấm thập phân.
SqlString Kiểu chuỗi ký tự có chiều dài thay đổi.
Bảng 5.6. Các kiểu dữ liệu Sql*
Trang 178
Giáo trình lập trình cơ sở dữ liệu
Để hiểu rõ cách sử dụng các phương thức dạng GetSql*, xét ví dụ đọc dữ liệu của
các cột ProductID, ProductName, UnitPrice, UnitsInStock và Discontinued của bảng
Products bởi đối tượng DataReader.
Để biết phương thức GetSql* nào được dùng để lấy dữ liệu từ một cột, ta lấy kiểu dữ
liệu SQL Server của cột đó dò trong bảng 5.7 để tìm phương thức cần dùng. Chẳng hạn,
cột ProductID có kiểu dữ liệu SQL Server là int, tìm trong bảng 5.7, ta biết phương thức
GetSqlInt32 sẽ được dùng để đọc dữ liệu từ cột ProductID.
COLUMN NAME SQL SERVER COLUMN TYPE GETSql* METHOD Sql* Return Type
ProductID int GetInt32() SqlInt32
ProductName nvarchar GetSqlString() SqlString
UnitPrice money GetSqlMoney() SqlMoney
UnitsInStock smallint GetSqlInt16() SqlInt16
Discontinued bit GetSqlBoolean() SqlBoolean
Bảng 5.8. Products Table Columns, Types and GetSql* Methods.
Giả sử, ta có một đối tượng SqlDataReader có tên là productsSqlDataReader dùng
để đọc giá trị của các cột trong bảng Products. Đoạn mã sau dùng một vòng lặp while để
duyệt qua các dòng trong DataReader và đọc giá trị của các cột bằng cách dùng các
phương thức GetSql* và kiểu dữ liệu Sql*.
while (productsSqlDataReader.Read())
{
SqlInt32 productID =
productsSqlDataReader.GetSqlInt32(productIDColPos);
Console.WriteLine("productID = " + productID);
SqlString productName =
productsSqlDataReader.GetSqlString(productNameColPos);
Console.WriteLine("productName = " + productName);
SqlMoney unitPrice =
productsSqlDataReader.GetSqlMoney(unitPriceColPos);
Console.WriteLine("unitPrice = " + unitPrice);
SqlInt16 unitsInStock =
productsSqlDataReader.GetSqlInt16(unitsInStockColPos);
Console.WriteLine("unitsInStock = " + unitsInStock);
SqlBoolean discontinued =
productsSqlDataReader.GetSqlBoolean(discontinuedColPos);
Console.WriteLine("discontinued = " + discontinued);
}
Trang 179
Giáo trình lập trình cơ sở dữ liệu
Chương trình hoàn chỉnh dưới đây minh họa cách đọc dữ liệu của các cột từ đối
tượng SqlDataReader bằng cách dùng các phương thức dạng GetSql* và các kiểu dữ
liệu Sql*.
Chương trình 5.3: Using the GetSql* Methods to Read Column Values
/*
StronglyTypedColumnValuesSql.cs illustrates how to read
column values as Sql* types using the GetSql* methods
*/
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
class StronglyTypedColumnValuesSql
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice, " +
"UnitsInStock, Discontinued " +
"FROM Products " +
"ORDER BY ProductID";
mySqlConnection.Open();
SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader();
int productIDColPos =
productsSqlDataReader.GetOrdinal("ProductID");
int productNameColPos =
productsSqlDataReader.GetOrdinal("ProductName");
int unitPriceColPos =
productsSqlDataReader.GetOrdinal("UnitPrice");
int unitsInStockColPos =
productsSqlDataReader.GetOrdinal("UnitsInStock");
int discontinuedColPos =
productsSqlDataReader.GetOrdinal("Discontinued");
Trang 180
Giáo trình lập trình cơ sở dữ liệu
while (productsSqlDataReader.Read())
{
SqlInt32 productID =
productsSqlDataReader.GetSqlInt32(productIDColPos);
Console.WriteLine("productID = " + productID);
SqlString productName =
productsSqlDataReader.GetSqlString(productNameColPos);
Console.WriteLine("productName = " + productName);
SqlMoney unitPrice =
productsSqlDataReader.GetSqlMoney(unitPriceColPos);
Console.WriteLine("unitPrice = " + unitPrice);
SqlInt16 unitsInStock =
productsSqlDataReader.GetSqlInt16(unitsInStockColPos);
Console.WriteLine("unitsInStock = " + unitsInStock);
SqlBoolean discontinued =
productsSqlDataReader.GetSqlBoolean(discontinuedColPos);
Console.WriteLine("discontinued = " + discontinued);
}
productsSqlDataReader.Close();
mySqlConnection.Close();
}
}
productID = 2
productName = Chang
unitPrice = 19
unitsInStock = 17
discontinued = False
productID = 3
productName = Aniseed Syrup
unitPrice = 10
unitsInStock = 13
discontinued = False
productID = 4
productName = Chef Anton's Cajun Seasoning
unitPrice = 22
unitsInStock = 53
discontinued = False
productID = 5
productName = Chef Anton's Gumbo Mix
unitPrice = 21.35
unitsInStock = 0
discontinued = True
Trang 181
Giáo trình lập trình cơ sở dữ liệu
object unitPriceObj =
productsSqlDataReader["UnitPrice"];
Xét ví dụ đọc một giá trị null từ cơ sở dữ liệu. Trong trường hợp này, ta thực hiện
một lệnh truy vấn SELECT để lấy giá trị trên cột UnitPrice của bảng Products. Theo
thiết kế, cột này có thể nhận giá trị null.
Để kiểm tra một cột có thể chứa giá trị null hay không, ta dùng phương thức
UsDBNull của đối tượng DataReader. Phương thức này trả về giá trị true cho biết cột
đó có thể chứa giá trị null và ngược lại. Ví dụ:
if (productsSqlDataReader.IsDBNull(unitPriceColPos))
{
Console.WriteLine("UnitPrice column contains a null value");
}
else
{
unitPrice = productsSqlDataReader.GetDecimal(unitPriceColPos);
}
Vì cột UnitPrice có thể chứa giá trị null nên kết quả trả về của hàm
productsSqlDataReader.IsDBNull(unitPriceColPos) là true. Vì thế, kết quả xuất ra trên
màn hình là.
UnitPrice column contains a null value
Để lưu một giá trị null, ta dùng kiểu dữ liệu dạng Sql*. Chẳng hạn:
SqlMoney unitPrice =
productsSqlDataReader.GetSqlMoney(unitPriceColPos);
Console.WriteLine("unitPrice = " + unitPrice);
Mỗi kiểu dữ liệu trong nhóm Sql* cũng có một thuộc tính tên IsNull cho biết đối tượng
thuộc kiểu này có bằng null hay không. Ví dụ:
Console.WriteLine("unitPrice.IsNull = " + unitPrice.IsNull);
Trang 182
Giáo trình lập trình cơ sở dữ liệu
Lưu ý: Các lệnh SELECT được phân tách nhau bởi dấu chấm phẩy “;”. Đồng thời,
phải xác định hình thức mà bạn muốn lấy dữ liệu: Lấy nhiều dòng ở nhiều bảng nhưng
không liên quan gì đến nhau hay lấy các dòng trên nhiều bảng có quan hệ với nhau.
Chẳng hạn, nếu muốn lấy tất cả các đơn đặt hàng của một khách hàng có mã
(CustomerID) là ALFKI, bạn không thể thực hiện hai lệnh SELECT tách biệt trên hai
bảng Customers và Orders. Thay vào đó, ta phải nối hai bảng với nhau như sau:
SELECT Customers.CustomerID, CompanyName, OrderID
FROM Customers, Orders
WHERE Customers.CustomerID = Orders.CustomerID
AND Customers.CustomerID = 'ALFKI';
Trang 183
Giáo trình lập trình cơ sở dữ liệu
Để chạy các lệnh SQL đã gán cho thuộc tính CommandText của đối tượng
mySqlCommand ở trên, ta gọi phương thức ExecuteReader đế lấy về đối tượng
SqlDataReader.
Lệnh này sẽ trả về 3 tập kết quả tương ứng với 3 lệnh SELECT. Để đọc dữ liệu từ
tập kết quả thứ nhất, ta dùng phương thức Read() của đối tượng SqlDataAdapter.
Phương thức này trả về giá trị false nếu không còn dòng nào để đọc.
Khi đọc hết các dòng trong tập đầu tiên, ta gọi hàm NextResult() của đối tượng
SqlDataReader để chuyển sang đọc dữ liệu trên tập kết quả thứ 2. Phương thức
NextResult cho phép DataReader chuyển sang tập kết quả tiếp theo và chỉ trả về false
nếu không còn tập dữ liệu để đọc.
Đoạn mã sau minh họa cách sử dụng các phương thức Read và NextResult để đọc
dữ liệu từ 3 tập kết quả của 3 lệnh SELECT.
do
{
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[0] = " +
mySqlDataReader[0]);
Console.WriteLine("mySqlDataReader[1] = " +
mySqlDataReader[1]);
}
} while (mySqlDataReader.NextResult());
Vòng lặp do … while dùng để kiếm tra giá trị trả về của hàm mySqlDataReader.
NextResult. Việc kiểm tra được thực hiện ở cuối vòng lặp, có nghĩa là các lệnh trong
thân vòng lặp này được thực hiện ít nhất một lần. Bạn phải gọi hàm NextResult ở cuối
vòng lặp vì trước tiên hàm này chuyển bộ đọc DataReader đến tập dữ liệu mới sau đó
mới trả về kết quả cho biết còn tập dữ liệu nào để chuyển đến nữa hay không. Nếu gọi
hàm NextResult theo cách thông thường bằng vòng lặp while, SqlDataReader sẽ bỏ qua
tập dữ liệu đầu tiên.
Chương trình sau minh họa cách sử dụng các phương thức NextResult và Read của
đối tượng SqlDataReader, phương thức ExecuteReader của SqlCommand để thực thi
nhiều truy vấn cùng lúc.
Trang 184
Giáo trình lập trình cơ sở dữ liệu
class ExecuteSelect
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
"ORDER BY CustomerID;" +
"SELECT TOP 6 OrderID, CustomerID " +
"FROM Orders " +
"ORDER BY OrderID;";
mySqlConnection.Open();
Console.WriteLine("mySqlDataReader[1] = " +
mySqlDataReader[1]);
}
Console.WriteLine(""); // visually split the results
} while (mySqlDataReader.NextResult());
mySqlDataReader.Close();
mySqlConnection.Close();
}
}
Trang 185
Giáo trình lập trình cơ sở dữ liệu
mySqlDataReader[0] = 3
mySqlDataReader[1] = Aniseed Syrup
mySqlDataReader[0] = 4
mySqlDataReader[1] = Chef Anton's Cajun Seasoning
mySqlDataReader[0] = 5
mySqlDataReader[1] = Chef Anton's Gumbo Mix
mySqlDataReader[0] = ALFKI
mySqlDataReader[1] = Alfreds Futterkiste
mySqlDataReader[0] = ANATR
mySqlDataReader[1] = Ana Trujillo3 Emparedados y helados
mySqlDataReader[0] = ANTON
mySqlDataReader[1] = Antonio Moreno Taquería
mySqlDataReader[0] = 10248
mySqlDataReader[1] = VINET
mySqlDataReader[0] = 10249
mySqlDataReader[1] = TOMSP
mySqlDataReader[0] = 10250
mySqlDataReader[1] = HANAR
mySqlDataReader[0] = 10251
mySqlDataReader[1] = VICTE
mySqlDataReader[0] = 10252
mySqlDataReader[1] = SUPRD
mySqlDataReader[0] = 10253
mySqlDataReader[1] = HANAR
mySqlCommand.CommandText =
"INSERT INTO Customers (CustomerID, CompanyName) " +
"VALUES ('J5COM', 'Jason 5 Company');" +
Bạn có thể sử dụng cùng một vòng lặp do … while như ví dụ ở phần trước để lấy hai
tập dữ liệu trả về bởi câu lệnh SELECT. Thậm chí, vòng lặp này vẫn thực hiện khi
không có câu lệnh SELECT trong nhóm lệnh được thực thi vì chỉ có lệnh SELECT mới
trả về tập dữ liệu kết quả và phương thức NextResult chỉ trả về giá trị true cho lệnh
SELECT, trả về giá trị false cho các lệnh SQL còn lại.
Chương trình sau minh họa cách sử dụng các phương thức NextResult, Read và
ExecuteReader để thực thi nhiều lệnh SQL xen kẽ nhau.
class ExecuteMultipleSQL
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"INSERT INTO Customers (CustomerID, CompanyName) " +
"VALUES ('J5COM', 'Jason 5 Company');" +
Trang 187
Giáo trình lập trình cơ sở dữ liệu
mySqlConnection.Open();
} while (mySqlDataReader.NextResult());
mySqlDataReader.Close();
mySqlConnection.Close();
}
}
mySqlDataReader[0] = J5COM
mySqlDataReader[1] = Another Jason Company
Trang 188
Giáo trình lập trình cơ sở dữ liệu
và sqlCommand1. (Xem mục Tạo đối tượng Command bằng Visual Studio .Net
trong chương 4)
- Cấu hình kết nối cho SqlCommand và gán lệnh truy vấn cho thuộc tính
CommandText.
- Tạo một ListView bằng cách kéo nó từ thẻ Common Controls của Toolbox vào
Form. Tên của ListView mặc định là listView1 (hình 5.1).
- Đặt giá trị thuộc tính Anchor của ListView là Top, Bottom, Left, Right.
- Đặt giá trị thuộc tính View của ListView là Details.
- Trong khung Properties của listView1, nhấp nút “…” bên phải thuộc tính
Column để thêm các cột vào ListView.
- Trong hộp thoại ColumnHeader Collection Editor, nhấn nút Add 3 lần để tạo 3
cột cho ListView (hình 5.2).
Trang 189
Giáo trình lập trình cơ sở dữ liệu
Hình 5.3. Thiết lập thuộc tính cho các cột của ListView
Trang 190
Giáo trình lập trình cơ sở dữ liệu
Trang 191
Giáo trình lập trình cơ sở dữ liệu
Trong chương này, bạn đã được học cách sử dụng đối tượng DataReader để đọc kết
quả trả về từ cơ sở dữ liệu. Các đối tượng DataReader đều thuộc nhóm lớp kết nối. Có 3
lớp DataReader: SqlDataReader, OleDbDataReader và OdbcDataReader. Đối tượng
SqlDataReader được dùng để đọc các dòng lấy được từ cơ sở dữ liệu SQL Server. Đối
tượng OleDbDataReader dùng để đọc các dòng từ cơ sở dữ liệu có hỗ trợ OLE DB (như
Access hay Oracle). Cuối cùng, OdbcDataReader được dùng để đọc các dòng từ cơ sở
dữ liệu có hỗ trợ ODBC.
Các đối tượng DataReader chỉ có thể dùng để đọc dữ liệu theo một chiều từ đầu đến
cuối nhưng không thể cập nhật các thay đổi về cơ sở dữ liệu. Các đối tượng DataReader
thường được dùng thay cho DataSet vì nó đọc dữ liệu nhanh hơn. Các đối tượng
DataSet cho phép bạn lưu một bản sao các dòng trong cơ sở dữ liệu. Ta có thể làm việc
với dữ liệu trong bản sao này ngay cả khi không có kết nối tới cơ sở dữ liệu.
1. Đối tượng DataReader dùng để làm gì? Trình bày các ưu, nhược điểm.
2. Viết hàm để thực hiện các yêu cầu sau:
a. Hiển thị danh sách sinh viên của một lớp
b. Hiển thị danh sách môn học được đăng ký bởi sinh viên SV
c. Hiển thị danh sách sinh viên đăng ký môn học MH
d. Tính tổng số tín chỉ mà sinh viên SV đăng ký và số tiền học phí phải trả
3. Viết hàm liệt kê những sinh viên đăng ký vượt quá số tín chỉ (25 tín chỉ) hoặc không
đủ số tín chỉ quy định (15 tín chỉ)
4. Tìm thông tin sinh viên theo một trong các tiêu chí sau: Mã số SV, tên, ngày sinh.
Trang 192
Giáo trình lập trình cơ sở dữ liệu
6. CHƯƠNG 6
LƯU TRỮ DỮ LIỆU VỚI DATASET
Chương này giới thiệu chi tiết về các tính năng và cách sử dụng của đối tượng
DataSet trong việc lưu trữ kết quả truy vấn trả về từ cơ sở dữ liệu.
Các đối tượng DataSet cho phép lưu trữ một bản sao thông tin của cơ sở dữ liệu. Từ
đó, bạn có thể xử lý trực tiếp trên các thông tin này trong khi kết nối đã bị ngắt. DataSet
là lớp dùng chung, thuộc nhóm không kết nối. Vì thế, nó làm việc với tất cả các cơ sở
dữ liệu. Không giống như DataReader, đối tượng DataSet còn cho phép đọc các dòng
theo thứ tự bất kỳ và có thể thay đổi dữ liệu trong các dòng đó.
Chương này cũng trình bày cách sử dụng đối tượng DataAdapter để đọc các dòng từ
cơ sở dữ liệu vào một DataSet. Lớp DataAdapter thuộc nhóm lớp kết nối và là cầu nối
giữa hai nhóm lớp: kết nối và không kết nối. Có 3 lớp DataAdapter là SqlDataAdapter,
OleDbDataAdapter và OdbcDataAdapter.
Đối tượng DataAdapter được dùng để sao chép các dòng từ cơ sở dữ liệu vào một
DataSet và cập nhật lại các thay đổi trong DataSet về cơ sở dữ liệu.
Trang 193
Giáo trình lập trình cơ sở dữ liệu
Hầu hết các phương thức, thuộc tính và sự kiện đều được xây dựng như nhau (cùng
tên) trong cả 3 lớp DataAdapter. Các bảng sau liệt kê tên các thuộc tính, phương thức,
sự kiện và ý nghĩa của chúng.
PROPERTY TYPE DESCRIPTION
AcceptChangesDuringFill bool Thuộc tính đọc - ghi. Cho biết phương
thức AcceptChange() có được gọi sau khi
một dòng (DataRow) được thêm vào, bị thay
đổi hay bị xóa khỏi một bảng (DataTable)
hay không. Giá trị mặc định là true.
ContinueUpdateOnError bool Thuộc tính đọc - ghi. Cho biết có tiếp
tục cập nhật cơ sở dữ liệu khi xảy ra lỗi
hay không. Giá trị mặc định là true.
Trang 194
Giáo trình lập trình cơ sở dữ liệu
Trang 195
Giáo trình lập trình cơ sở dữ liệu
Ví dụ 1:
Đoạn mã sau minh họa cách tạo một đối tượng SqlDataAdapter dùng phương thức
khởi tạo đầu tiên SqlDataAdapter().
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();
Trước khi sử dụng đối tượng mySqlDataAdapter để thao tác với DataSet, ta phải gán
một đối tượng SqlCommand chứa lệnh SELECT hoặc tên của một thủ tục cho thuộc
tính SelectCommand.
Đoạn mã sau minh họa cách tạo một đối tượng SqlCommand với thuộc tính
CommandText là lệnh SELECT để lấy dữ liệu 5 dòng đầu tiên trên các cột ProductID,
ProductName và UnitPrice trong bảng Products của cơ sở dữ liệu Northwind. Sau đó,
gán đối tượng SqlCommand này cho thuộc tính SelectCommand của SqlDataAdapter.
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
mySqlDataAdapter.SelectCommand = mySqlCommand;
Ví dụ 2: Tạo đối tượng SqlDataAdapter bởi phương thức khởi tạo thứ 2
SqlDataAdapter mySqlDataAdapter =
new SqlDataAdapter(mySqlCommand);
Ví dụ 3: Tạo đối tượng SqlDataAdapter bởi phương thức khởi tạo thứ 3.
Trang 196
Giáo trình lập trình cơ sở dữ liệu
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
string selectCommandString =
"SELECT TOP 10 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
SqlDataAdapter mySqlDataAdapter =
new SqlDataAdapter(selectCommandString, mySqlConnection);
Ví dụ 4: Tạo đối tượng SqlDataAdapter bởi phương thức khởi tạo thứ 4.
string selectCommandString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";
string connectionString =
"SELECT TOP 10 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
SqlDataAdapter mySqlDataAdapter =
new SqlDataAdapter(selectCommandString, connectionString);
Phương thức khởi tạo này yêu cầu đối tượng SqlDataAdapter phải tạo một đối tượng
SqlConnection riêng. Điều này có thể gây rắc rối và cả về mặt hiệu suất vì việc mở một
kết nối tới cơ sở dữ liệu dùng đối tượng SqlConnection chiếm thời gian đáng kể. Thay
vào đó, bạn nên dùng phương thức khởi tạo sử dụng một đối tượng SqlConnection có
sẵn làm tham số (cách thứ 3).
Đối tượng SqlDataAdapter không lưu trữ các dòng dữ liệu. Vai trò của nó đơn thuần
chỉ là một ỗng dẫn hay cầu nối từ cơ sở dữ liệu đến đối tượng DataSet.
Trang 197
Giáo trình lập trình cơ sở dữ liệu
Trang 198
Giáo trình lập trình cơ sở dữ liệu
Trang 199
Giáo trình lập trình cơ sở dữ liệu
Trong đó: dataSetNameString là tên của DataSet (kiểu chuỗi) được gán cho thuộc
tính DataSetName của đối tượng DataSet cần tạo. Tên của DataSet có thể có hoặc
không. Nếu có sử dụng, bạn nên đặt cho nó một tên có ý nghĩa và mang tính gợi nhớ.
Trang 200
Giáo trình lập trình cơ sở dữ liệu
Đoạn mã trên tạo một đối tượng SqlCommand chứa lệnh SELECT để lấy dữ liệu
trên các của ProductID, ProductName và UnitPrice của 5 dòng đầu tiên trong bảng
Products.
Lưu ý: Bạn có thể thực hiện lệnh truy vấn SELECT để lấy dữ liệu trên nhiều bảng
kết hợp bằng cách dùng mệnh đề JOIN. Tuy nhiên, nên tránh cách làm này vì một
DataTable chỉ dùng để lưu một bảng trong cơ sở dữ liệu.
Tiếp theo, đưa dữ liệu trả về từ truy vấn vào DataSet bằng cách gọi phương thức Fill
của đối tượng SqlDataAdapter.
Trang 201
Giáo trình lập trình cơ sở dữ liệu
Phương thức Fill() trả về số nguyên cho biết số dòng đã lấy và được đưa vào
DataSet bởi đối tượng DataAdapter. Trong ví dụ này, kết quả trả về là 5.
Tham số thứ nhất của phương thức Fill() là DataSet dùng để lưu kết quả. Tham số
thứ hai là một chuỗi, đây là tên mà bạn muốn gán cho đối tượng DataTable được tạo ra
trong DataSet.
Tên mà bạn muốn gán cho DataTable không nhất thiết phải trùng với tên bảng trong
cơ sở dữ liệu. Bạn có thể dùng một tên bất kỳ, miễn sao nó mang tính gợi nhớ và biểu
thị được nội dung trả về của truy vấn. Tuy nhiên, các DataTable thường dùng để chứa
dữ liệu trong một bảng tương ứng của cơ sở dữ liệu, vì thế bạn nên lấy cùng tên để tiện
cho việc xử lý.
Khi phương thức Fill() lần đầu tiên được gọi, các bước sau sẽ được thực hiện
Lệnh SELECT trong đối tượng SqlCommand sẽ được thực thi.
Một đối tượng DataTable mới được tạo ra trong DataSet.
Đưa dữ liệu trả về bởi lệnh SELECT vào DataSet.
Sau khi gọi lệnh Fill(), cần phải đóng kết nối cơ sở dữ liệu để giải phóng tài nguyên
hệ thống.
// Đóng kết nối CSDL
mySqlConnection.Close();
Thực tế, phương thức Fill() sẽ tự mở và đóng đối tượng Connection nếu bạn không
thực hiện điều này. Tuy nhiên, tốt hơn hết là mở và đóng kết nối một cách rõ ràng để
chương trình thực thi theo đúng những gì đã vạch ra. Mặc khác, nếu phải dùng phương
thức Fill() nhiều lần trong một đoạn mã ngắn, ta nên giữ kết nối ở trạng thái Open và
chỉ đóng khi đã thực hiện xong tất cả.
Sau khi đưa dữ liệu vào DataSet (thực chất là một DataTable). Ta có thể đọc dữ liệu
từ DataTable trong DataSet đó. Để lấy một DataTable từ DataSet, ta đặt tên của
DataTable hoặc chỉ số của bảng trong cặp dấu ngoặc vuông [ ] ngay sau thuộc tính
Tables của đối tượng DataSet.
DataTable myDataTable = myDataSet.Tables["Products"];
DataTable myDataTable = myDataSet.Tables[0];
Đoạn mã sau sử dụng một vòng lặp for each duyệt qua các dòng (DataRow) của
bảng kết quả (DataTable) để xuất giá trị các cột ra màn hình.
foreach (DataRow myDataRow in myDataTable.Rows)
{
Console.WriteLine("ProductID = " + myDataRow["ProductID"]);
Console.WriteLine("ProductName = " + myDataRow["ProductName"]);
Console.WriteLine("UnitPrice = " + myDataRow["UnitPrice"]);
}
Thuộc tính Rows có kiểu DataRowCollection cho phép truy xuất đến tất cả các dòng
hay các đối tượng DataRow được lưu trong DataTable. Để đọc dữ liệu trên các cột của
Trang 202
Giáo trình lập trình cơ sở dữ liệu
DataRow, đặt tên cột hoặc chỉ số của cột trong cặp dấu ngoặc vuông [ ] ngay sau đối
tượng DataRow.
Chẳng hạn, để lấy giá trị trên cột ProductID, ta viết myDataRow[“ProductID”] hoặc
myDataRow[0] vì ProductID là cột đầu tiên nên có chỉ số là 0.
Đoạn mã sau minh họa các đọc dữ liệu trên tất cả các dòng, các cột của mọi
DataTable được lưu trong DataSet.
foreach (DataTable myDataTable in myDataSet.Tables)
{
foreach (DataRow myDataRow in myDataTable.Rows)
{
foreach (DataColumn myDataColumn in myDataTable.Columns)
{
Console.WriteLine(myDataColumn + "= " +
myDataRow[myDataColumn]);
}
}
}
Trong ví dụ này, ta không cần biết hay dùng đến tên bảng (DataTable) hay tên cột
(DataColumn).
Chương trình sau minh họa cách đưa các dòng từ cơ sở dữ liệu trả về bởi câu lệnh
SELECT vào một DataSet. Sau đó, xuất các giá trị của tập kết quả này ra màn hình.
Chương trình 6.1: Đưa dữ liệu vào DataSet dùng phương thức Fill
/*
PopulateDataSetUsingSelect.cs illustrates how to populate a DataSet
object using a SELECT statement
*/
using System;
using System.Data;
using System.Data.SqlClient;
class PopulateDataSetUsingSelect
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
Trang 203
Giáo trình lập trình cơ sở dữ liệu
mySqlConnection.Open();
mySqlConnection.Close();
Console.WriteLine("ProductName = " +
myDataRow["ProductName"]);
Console.WriteLine("UnitPrice = " +
myDataRow["UnitPrice"]);
}
}
}
Trang 204
Giáo trình lập trình cơ sở dữ liệu
ProductID = 3
ProductName = Aniseed Syrup
UnitPrice = 10
ProductID = 4
ProductName = Chef Anton's Cajun Seasoning
UnitPrice = 22
ProductID = 5
ProductName = Chef Anton's Gumbo Mix
UnitPrice = 21.35
Tập các dòng muốn lấy có chỉ số bắt đầu từ startRow đến startRow + numOfRows
và được lưu vào một DataTable. Tất cả các phương thức Fill() đều trả về một số nguyên
cho biết số dòng lấy được từ cơ sở dữ liệu.
Ví dụ sau minh họa cách sử dụng của phương thức Fill() cuối dùng. Đối tượng
DataAdapter nhận về 5 dòng từ bảng Products bởi một lệnh truy vấn SELECT nhưng
chỉ lưu 3 dòng vào một DataTable, bắt đầu dòng thứ 2 (dòng thứ nhất có chỉ số là 0,
dòng thứ 2 chỉ số là 1).
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
mySqlDataAdapter.SelectCommand = mySqlCommand;
Trang 205
Giáo trình lập trình cơ sở dữ liệu
Tham số numOfRows trong ví dụ trên được đặt là 3, tương ứng với số dòng mà
DataAdapter chuyển cho DataSet. Lưu ý là đối tượng DataAdapter vẫn nhận về tất cả
các dòng theo truy vấn nhưng thực sự chỉ có 3 dòng được đưa vào DataSet.
Chương trình sau minh họa cách sử dụng phương thức Fill() với 4 tham số để lấy
một phần dữ liệu từ tập kết quả trả về bởi lệnh truy vấn SELECT như trong ví dụ trên.
Chương trình 6.2: Lấy một phần dữ liệu kết quả dùng phương thức Fill
/*
PopulateDataSetUsingRange.cs illustrates how to populate a DataSet
object with a range of rows from a SELECT statement
*/
using System;
using System.Data;
using System.Data.SqlClient;
class PopulateDataSetUsingRange
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
mySqlDataAdapter.SelectCommand = mySqlCommand;
int numberOfRows =
mySqlDataAdapter.Fill(myDataSet, 1, 3, "Products");
mySqlConnection.Close();
Trang 206
Giáo trình lập trình cơ sở dữ liệu
Console.WriteLine("ProductName = " +
myDataRow["ProductName"]);
Console.WriteLine("UnitPrice = " +
myDataRow["UnitPrice"]);
}
}
}
ProductID = 2
ProductName = Chang
UnitPrice = 19
ProductID = 3
ProductName = Aniseed Syrup
UnitPrice = 10
ProductID = 4
ProductName = Chef Anton's Cajun Seasoning
UnitPrice = 22
Trang 207
Giáo trình lập trình cơ sở dữ liệu
được gán là ALFKI) được gửi vào Command qua phương thức Add của thuộc tính
Parameters.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"EXECUTE CustOrderHist @CustomerID";
mySqlCommand.Parameters.Add("@CustomerID",
SqlDbType.NVarChar, 5).Value = "ALFKI";
Sau đó, sử dụng phương thức Fill() của đối tượng DataAdapter để đưa kết quả truy
vấn bởi thủ tục CustOrderHist vào một DataSet.
mySqlConnection.Open();
int numberOfRows = mySqlDataAdapter.Fill(myDataSet, "CustOrderHist");
mySqlConnection.Close();
Thực tế, trong ví dụ này, DataSet sẽ tạo một DataTable mới có tên CustOrderHist
(lấy từ tham số thứ 2 của phương thức Fill) để lưu kết quả truy vấn. Sau đây là chương
trình hoàn chỉnh minh họa các bước của ví dụ này.
Chương trình 6.3: Lấy kết quả thực thi Stored Procedure bằng phương thức Fill.
/*
PopulateDataSetUsingProcedure.cs illustrates how to populate a
DataSet object using a stored procedure
*/
using System;
using System.Data;
using System.Data.SqlClient;
class PopulateDataSetUsingProcedure
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"EXECUTE CustOrderHist @CustomerID";
mySqlCommand.Parameters.Add(
"@CustomerID", SqlDbType.NVarChar, 5).Value = "ALFKI";
Trang 208
Giáo trình lập trình cơ sở dữ liệu
Console.WriteLine(
"Retrieving rows from the CustOrderHist() Procedure");
int numberOfRows =
mySqlDataAdapter.Fill(myDataSet, "CustOrderHist");
mySqlConnection.Close();
DataTable myDataTable = myDataSet.Tables["CustOrderHist"];
mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID;" +
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI';";
Khi những lệnh SELECT này được thực thi, có hai tập kết quả sẽ được trả về: một
tập chứa 2 dòng lấy từ bảng Products, tập thứ hai chứa một dòng từ bảng Customers.
Hai tập kết quả này sẽ được lưu vào hai DataTable riêng biệt.
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();
mySqlDataAdapter.SelectCommand = mySqlCommand;
Trong đoạn mã trên, phương thức Fill() không có tham số tên của DataTable, nó chỉ
dùng một tham số là DataSet lưu các tập kết quả. Trong trường hợp này, hai DataTable
được tự động tạo ra trong DataSet sẽ có tên mặc định là Table và Table1. Bảng có tên
Table chứa kết quả trả về từ bảng Products bởi lệnh SELECT thứ nhất và bảng có tên
Table1 chưa kết quả trả về từ bảng Customers bởi lệnh SELECT thứ 2.
Tên của một DataTable được lưu trong của tính TableName của nó và có thể thay
đổi được. Chẳng hạn, để đổi tên Table và Table1 thành Products và Customer, ta viết:
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Customers";
Sau đây là chương trình hoàn chỉnh minh họa cách thực hiện nhiều truy vấn
SELECT và lưu kết quả của từng truy vấn vào các DataTable của cùng một DataSet.
Chương trình 6.4: Lưu dữ liệu trả về từ nhiều lệnh SELECT vào nhiều DataTable
của cùng một DataSet
/*
MutlipleDataTables.cs illustrates how to populate a DataSet
with multiple DataTable objects using multiple SELECT statements
Trang 210
Giáo trình lập trình cơ sở dữ liệu
*/
using System;
using System.Data;
using System.Data.SqlClient;
class MultipleDataTables
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID;" +
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI';";
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Customers";
Trang 211
Giáo trình lập trình cơ sở dữ liệu
Cách 2: Thay đổi giá trị thuộc tính CommandText của SelectCommand
Ta cũng có thể đưa dữ liệu vào nhiều DataTable trong cùng một DataSet bằng cách
thay đổi giá trị cho thuộc tính của SelectCommand trong DataAdapter trước mỗi lần gọi
phương thức Fill().
Đoạn mã sau minh họa cách thực thi một truy vấn để lấy 2 dòng trên bảng Products
và đưa vào một DataTable. Sau khi gọi phương thức Fill(), DataSet sẽ chứa một
DataTable có tên “Products”.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
Tiếp theo, thay đổi giá trị thuộc tính CommandText của SelectionCommand trong
đối tượng SqlDataAdapter bởi một lệnh SELECT khác để lấy về các dòng trong bảng
Customers. Sau đó, gọi lại phương thức Fill() của đối tượng SqlDataAdapter. Lúc này,
DataSet chứa hai DataTable có tên: “Products” và “Customers”
mySqlDataAdapter.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";
Trang 212
Giáo trình lập trình cơ sở dữ liệu
Chương trình hoàn chỉnh sau minh họa đầy đủ các bước để lấy dữ liệu từ các bảng
Products và Customers trong cơ sở dữ liệu Northwind. Sau đó, đưa kết quả vào hai
DataTable trong cùng một DataSet bằng cách thay đổi các lệnh truy vấn trong thuộc
tính CommandText và gọi lại hàm Fill() nhiều lần.
Chương trình 6.5: Đưa dữ liệu vào nhiều DataTable của cùng một DataSet bằng
cách thay đổi lệnh truy vấn
/*
MutlipleDataTables2.cs illustrates how to populate a DataSet
object with multiple DataTable objects by changing the
CommandText property of a DataAdapter object's SelectCommand
*/
using System;
using System.Data;
using System.Data.SqlClient;
class MultipleDataTables2
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
mySqlDataAdapter.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";
mySqlConnection.Close();
Trang 213
Giáo trình lập trình cơ sở dữ liệu
{
Console.WriteLine("\nReading from the " +
myDataTable.TableName + " DataTable");
mySqlDataAdapter2.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";
Trang 214
Giáo trình lập trình cơ sở dữ liệu
Sau đây là chương trình hoàn chỉnh minh họa chi tiết các bước để thực hiện ý tưởng
trên.
Chương trình 6.6: Đưa dữ liệu vào nhiều DataTable của cùng DataSet dùng nhiều
đối tượng DataAdapter khác nhau
/*
MutlipleDataTables3.cs illustrates how to populate a DataSet
object with multiple DataTable objects using multiple
DataAdapter objects to populate the same DataSet object
*/
using System;
using System.Data;
using System.Data.SqlClient;
class MultipleDataTables3
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
int numberOfRows =
mySqlDataAdapter1.Fill(myDataSet, "Products");
mySqlDataAdapter2.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";
Trang 215
Giáo trình lập trình cơ sở dữ liệu
{
Console.WriteLine("\nReading from the " +
myDataTable.TableName + " DataTable");
6.5. Trộn các DataRow, DataTable và DataSet vào một DataSet khác
Trong trường hợp bạn có nhiều nguồn dữ liệu có cùng cấu trúc và ý nghĩa, bạn có
thể trộn chúng với nhau để có một tập dữ liệu duy nhất.
Chẳng hạn, bạn lấy dữ liệu về khách hàng từ nhiều văn phòng đại diện và gửi về
tổng công ty. Với trường hợp này, dữ liệu từ nhiều nguồn cần được trộn chung vào một
DataSet duy nhất.
Lớp DataSet cho phép thực hiện được điều này bằng cách cung cấp phương thức
Merge() với nhiều dạng khác nhau để cho phép trộn các dòng (DataRow), các bảng
(DataTable) và thậm chí là các DataSet vào một DataSet khác.
Các dạng của phương thức Merge:
void Merge(DataRow[] myDataRows)
void Merge(DataSet myDataSet)
void Merge(DataTable myDataTable)
void Merge(DataSet myDataSet, bool preserveChanges)
void Merge(DataRow[] myDataRows, bool preserveChanges,
MissingSchemaAction myMissingSchemaAction)
void Merge(DataSet myDataSet, bool preserveChanges,
MissingSchemaAction myMissingSchemaAction)
Trang 216
Giáo trình lập trình cơ sở dữ liệu
Trong đó:
preserveChanges: cho biết các thay đổi trong DataSet hiện tại (DataSet có
phương thức Merge được gọi) có được lưu lại hay không.
myMissingSchemaAction: chỉ ra cách xử lý khi DataSet hiện tại không có các
bảng hay các cột giống với DataRow, DataTable hay DataSet đang được trộn
chung vào một DataSet khác (nghĩa là các DataSet có cấu trúc khác nhau).
Tham số này nhận một trong các giá trị được định nghĩa bởi enum
System.Data.Miss-ingSchemaAction. Bảng sau liệt kê các giá trị khả dĩ của enum
MissingSchemaAction
CONSTANT DESCRIPTION
Add Cột hay bảng sẽ được thêm vào DataSet hiện tại. Add là giá trị
mặc định.
AddWithKey Cột và thông tin khóa chính được thêm vào DataSet hiện tại.
Error Phát sinh ra lỗi (SystemException).
Ignore Cột hay bảng sẽ bị loại bỏ và không được đọc.
Bảng 6.7. Enum System.Data.MissingSchemaAction
Chương trình sau minh họa cách trộn DataRow, DataTable, DataSet vào một DataSet
khác bằng cách gọi phương thức Merge() của lớp DataSet.
Chương trình 6.7: Trộn DataRow, DataTable, DataSet với một DataSet
/*
Merge.cs illustrates how to use the Merge() method
*/
using System;
using System.Data;
using System.Data.SqlClient;
class Merge
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
Trang 217
Giáo trình lập trình cơ sở dữ liệu
mySqlConnection.Open();
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"WHERE CustomerID IN ('AROUT', 'BERGS')";
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
mySqlConnection.Close();
myDataSet.Merge(myDataSet2);
Trang 218
Giáo trình lập trình cơ sở dữ liệu
CustomerID = ANATR
CompanyName = Ana Trujillo3 Emparedados y helados
ContactName = Ana Trujillo
Address = Avda. de la Constitución 2222
CustomerID = ANTON
CompanyName = Antonio Moreno Taquería
ContactName = Antonio Moreno
Address = Mataderos 2312
CustomerID = BERGS
CompanyName = Berglunds snabbköp
ContactName = Christina Berglund
Address = Berguvsvägen 8
ProductID = 2
ProductName = Chang
UnitPrice = 19
ProductID = 3
ProductName = Aniseed Syrup
UnitPrice = 10
ProductID = 4
ProductName = Chef Anton's Cajun Seasoning
UnitPrice = 22
ProductID = 5
ProductName = Chef Anton's Gumbo Mix
UnitPrice = 21.35
Trang 219
Giáo trình lập trình cơ sở dữ liệu
Đoạn mã sau sử dụng một đối tượng DataAdapter để thực thi lệnh truy vấn SELECT
ở trên và đưa dữ liệu vào một DataSet.
mySqlCommand.CommandText =
"SELECT CustomerID AS MyCustomer, CompanyName, Address " +
"FROM Customers AS Cust " +
"WHERE CustomerID = 'ALFKI'";
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();
Trong ví dụ trên, DataTable mới được tạo để lưu kết quả có tên là Customers, quy
định bởi tham số thứ 2 của phương thức Fill.
Để ánh xạ một DataTable trong DataSet, ta tạo một đối tượng DataTableMapping
dùng phương thức Add(). Lớp DataTableMapping thuộc namespace System.Data.
Common.
Đoạn mã sau tạo một đối tượng DataTableMapping có tên myDataTableMapping
để tạo ánh xạ tên bảng “Customers” thành “Cust” bằng cách gửi hai giá trị này vào tham
số của phương thức Add().
DataTableMapping myDataTableMapping =
Trang 220
Giáo trình lập trình cơ sở dữ liệu
mySqlDataAdapter.TableMappings.Add("Customers", "Cust");
Trong ví dụ này, phương thức Add được gọi qua thuộc tính TableMappings của đối
tượng DataAdapter. Thuộc tính này trả về một đối tượng DataTableMappingCollection.
Đối tượng này chứa một tập các DataTableMapping. Mỗi đối tượng DataTableMapping
dùng để ánh xạ tên của một DataTable thành một tên khác.
Để đọc ánh xạ này, ta dùng các thuộc tính SourceTable và DataSetTable của đối
tượng DataTableMapping. Chẳng hạn:
Console.WriteLine("myDataTableMapping.SourceTable = " +
myDataTableMapping.SourceTable);
Console.WriteLine("myDataTableMapping.DataSetTable = " +
myDataTableMapping.DataSetTable);
Một cách khác để đổi tên một DataTable trong DataSet là gán một tên mới cho thuộc
tính TableName của DataTable đó. Tuy nhiên, cách này dễ gây nhầm lẫn khi bạn đã tạo
một ánh xạ tương tự trên tên của DataTable.
myDataSet.Tables["Customers"].TableName = "Cust";
Tiếp theo, để đặt tên mới cho cột CustomerID là MyCustomer, ta gọi phương thức
Add() qua thuộc tính ColumnMappings của đối tượng DataTableMapping.
myDataTableMapping.ColumnMappings.Add("CustomerID", "MyCustomer");
Thuộc tính ColumnMappings trả về một đối tượng thuộc kiểu DataColumnMapping-
Collection. Đối tượng này chứa một tập các DataColumnMapping. Mỗi đối tượng
DataColumnMapping được dùng để ánh xạ tên một cột trong cơ sở dữ liệu thành một
tên khác cho DataColumn.
Đoạn chương trình sau minh họa cách dùng cách đối tượng DataTableMapping và
Data-ColumnMapping để ánh xạ tên một bảng và một cột trong cơ sở dữ liệu thành một
tên mới.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;
class Mappings
{
public static void Main()
{
SqlConnection mySqlConnection =
Trang 221
Giáo trình lập trình cơ sở dữ liệu
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlCommand.CommandText =
"SELECT CustomerID AS MyCustomer, CompanyName, Address " +
"FROM Customers AS Cust " +
"WHERE CustomerID = 'ALFKI'";
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();
DataTableMapping myDataTableMapping =
mySqlDataAdapter.TableMappings.Add("Customers", "Cust");
myDataSet.Tables["Customers"].TableName = "Cust";
Console.WriteLine("myDataTableMapping.SourceTable = " +
myDataTableMapping.SourceTable);
Console.WriteLine("CompanyName = " +
myDataRow["CompanyName"]);
Trang 222
Giáo trình lập trình cơ sở dữ liệu
CustomerID = ALFKI
CompanyName = Alfreds Futterkiste
Address = Obere Str. 57
Trang 223
Giáo trình lập trình cơ sở dữ liệu
Trang 224
Giáo trình lập trình cơ sở dữ liệu
Bạn cũng có thể tạo một đối tượng DataTable có định kiểu bằng cách dùng phương
thức khởi tạo của lớp MyDataSet.CustomersDataTable và đưa dữ liệu lấy được từ bảng
Customers vào DataTable đó.
Ví dụ: Đoạn mã sau minh họa một phương thức Form1_Load sử dụng DataSet có
định kiểu để lấy giá trị trên các cột CustomerID, CompanyName và Address của bảng
Customers và hiển thị chúng lên một ListView có tên là listView1. Để làm được điều
này, bạn nhấp đôi chuột lên Form1 trong màn hình thiết kế Form để mở khung viết mã
chương trình và tạo phương thức Form1_Load.
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Address " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";
sqlConnection1.Open();
mySqlDataAdapter.Fill(myDataSet, "Customers");
sqlConnection1.Close();
Trang 225
Giáo trình lập trình cơ sở dữ liệu
item.SubItems.Add(myDataRow.CompanyName);
item.SubItems.Add(myDataRow.Address);
}
}
Trang 226
Giáo trình lập trình cơ sở dữ liệu
- Trong mục Which data connection …, chọn kết nối cơ sở dữ liệu muốn sử dụng.
Nếu chưa có kết nối nào, nhấn nút New Connection để tạo mới một kết nối (hình
6.8). Nhấn nút Next.
- Trong cửa sổ Choose a Command Type, chọn dạng truy vấn muốn sử dụng (hình
6.9). Nhấn nút Next.
o Use SQL Statements: Sử dụng trực tiếp một lệnh truy vấn
o Creates new Stored Procedures: Tạo một lệnh truy vấn và lưu vào Stored
Procedure. Command sẽ gọi thủ tục này để lấy kết quả.
o Use existing Stored Procedures: Sử dụng một thủ tục đã có trong cơ sở
dữ liệu.
Trang 227
Giáo trình lập trình cơ sở dữ liệu
- Tạo lệnh truy vấn bằng cách nhập trực tiếp vào khung What data should… hoặc
sử dụng trình Query Builder bằng cách nhấn vào nút Query Builder. Nhấn nút
Next (hình 6.10)
- Trong cửa sổ Wizard Results, nhấn nút Finish để kết thúc quá trình tạo Data
Adapter (hình 6.11). Trong bước này, Wizard dựa vào lệnh SELECT mà bạn
nhập để tự động tạo ra các lệnh truy vấn khác như INSERT, UPDATE và
DELETE.
- Một đối tượng SqlDataAdapter được tạo ra và nằm ở khung bên dưới Form1
(hình 6.12).
Ngoài ra, cần phải gắn một kết nối cho thuộc tính Connection của SelectCommand
trong sqlDataAdapter1 trước khi DataAdapter có thể truy xuất đến cơ sở dữ liệu. Thực
hiện điều này bằng cách mở rộng thuộc tính SelectCommand của sqlDataAdapter1
trong khung Properties, nhấp chuột vào DropDownList bên cạnh thuộc tính Connection,
chọn Existing rồi chọn đối tượng kết nối cần sử dụng, trong trường hợp này là
sqlConnection1 (hình 6.13).
Trang 228
Giáo trình lập trình cơ sở dữ liệu
Trang 229
Giáo trình lập trình cơ sở dữ liệu
Trang 230
Giáo trình lập trình cơ sở dữ liệu
Mặt khác, bạn cũng cần phải kiểm tra lại chuỗi thông tin kết nối của đối tượng
Connection bằng cách mở rộng thuộc tính Connection trong SelectCommand, nha6po1
chuột vào DropDownList bên cạnh thuộc tính ConnectionString.
Để cấu hình lại lệnh truy vấn và phương thức thực thi lệnh hoặc xem trước dữ liệu
trả về, nhấp phải chuột vào đối tượng SqlDataAdapter bên dưới Form, chọn Configure
Data Adapter và Preview Data
Trang 231
Giáo trình lập trình cơ sở dữ liệu
Hình 6.14. Xem trước kết quả truy vấn qua DataAdapter
Trang 232
Giáo trình lập trình cơ sở dữ liệu
Trong Form1, nhắp phải chuột lên đối tượng sqlDataAdapter1 nằm ở khung bên
dưới Form, chọn Generate DataSet.
Trong hộp thoại Generate DataSet, chọn mục New, nhấn OK.
Một đối tượng DataSet mới có tên dataSet11 được thêm vào khung bên dưới Form
(hình 6.17). Tiếp theo, ta viết mã lệnh để hiển thị kết quả lên màn hình thông qua một
ListView. Trong hàm xử lý sự kiện Form_Load, thêm đoạn mã sau:
Trang 233
Giáo trình lập trình cơ sở dữ liệu
System.Data.DataTable myDataTable =
dataSet11.Tables["Products"];
item.SubItems.Add(myDataRow["ProductName"].ToString());
item.SubItems.Add(myDataRow["UnitPrice"].ToString());
}
}
Kết quả chạy chương trình
Trong chương này, bạn đã được học cách dùng đối tượng DataSet để lưu trữ kết quả
truy vấn từ cơ sở dữ liệu. các DataSet cho phép bạn lưu một bản sao các dòng và các
bảng từ cơ sở dữ liệu và bạn có thể làm việc, thao tác với dữ liệu trên bản sao này ngay
cả khi kết nối đã bị ngắt.
Không giống như các đối tượng trong nhóm lớp kết nối – chẳng hạn như
SqlDataReader – các DataSet là đối tượng dùng chung cho tất cả các loại cơ sở dữ liệu.
DataSet cũng cho phép bạn đọc các dòng theo thứ tự bất kỳ và cập nhật dữ liệu trên các
dòng đó.
Trang 234
Giáo trình lập trình cơ sở dữ liệu
Bạn cũng đã được học cách dùng đối tượng DataAdapter để đọc dữ liệu từ cơ sở dữ
liệu và đưa kết quả vào DataSet. DataAdapter là một phần của nhóm lớp kết nối. Có 3
lớp DataAdapter: SqlDataAdapter, OleDbDataAdapter và OdbcDataAdapter.
DataAdapter cũng được dùng để đưa các dòng từ DataSet vào cơ sở dữ liệu nhằm
đồng bộ các thay đổi được tạo ra trên bản sao dữ liệu trong DataSet với cơ sở dữ liệu.
Chẳng hạn, bạn có thể đọc các dòng từ cơ sở dữ liệu, đưa vào DataSet thông qua
DataAdapter, cập nhật giá trị trên một vài dòng của DataSet. Sau đó, cập nhật các thay
đổi lên cơ sở dữ liệu thông qua đối tượng DataAdapter.
Viết chương trình trên nền Windows Form để thực hiện các yêu cầu sau:
1. Tạo Form cho phép hiển thị danh sách sinh viên, thêm, cập nhật và xóa thông tin
một sinh viên.
2. Tạo Form cho phép hiển thị danh sách các môn học, thêm, cập nhật và xóa thông
tin một môn học.
3. Tạo Form cho phép xem kết quả đăng ký học phần của một sinh viên.
4. Tạo Form cho phép hiển thị danh sách sinh viên đăng ký môn học MH
5. Tạo Form cho phép hiển thị 2 bảng. Một bảng chứa danh sách sinh viên, khi
nhấp chuột chọn một sinh viên thì hiển thị danh sách các môn học mà sinh viên
đó đăng ký lên bảng thứ 2.
Trang 235
Giáo trình lập trình cơ sở dữ liệu
7. CHƯƠNG 7
CẬP NHẬT DỮ LIỆU DÙNG DATASET
Trong chương trước, chúng ta đã tìm hiểu cách sử dụng một DataSet để lưu một bản
sao các dòng lấy được từ cơ sở dữ liệu. Ngoài khả năng lưu trữ, DataSet còn cho phép
bạn xử lý dữ liệu và tạo ra các thay đổi trên dữ liệu đó ngay cả khi kết nối đã bị ngắt.
Chương này trình bày cách cập nhật dữ liệu trong DataSet, sau đó đồng bộ hóa các thay
đổi với cơ sở dữ liệu ở Server thông qua một đối tượng DataAdapter.
Trang 236
Giáo trình lập trình cơ sở dữ liệu
Trang 237
Giáo trình lập trình cơ sở dữ liệu
Trang 238
Giáo trình lập trình cơ sở dữ liệu
Trang 239
Giáo trình lập trình cơ sở dữ liệu
Trang 240
Giáo trình lập trình cơ sở dữ liệu
Giá trị của cột trên từng hàng là duy nhất hay không – được quy định bởi thuộc
tính Unique.
Lưu ý:
ADO.Net không tự động sinh ra giá trị cho các cột định danh trên mỗi dòng mới.
Chỉ có cơ sở dữ liệu mới thực hiện chức năng này. Bạn phải đọc giá trị định danh được
sinh tự động này từ cơ sở dữ liệu bởi hàm SCOPE_IDENTITY() hoặc hằng
@@IDENTITY. Tương tự, nếu bảng trong cơ sở dữ liệu chứa các cột được gán giá trị
mặc định, giá trị này cũng phải được đọc từ cơ sở dữ liệu thay vì gán trực tiếp cho thuộc
tính DefaultValue của DataColumn. Giá trị mặc định được gán khi định nghĩa cấu trúc
cơ sở dữ liệu có thể thay đổi thường xuyên, vì thế ta phải đọc các giá trị mới này từ cơ
sở dữ liệu hơn là thay đổi lại mã nguồn.
Bằng cách thêm các ràng buộc này, ta có thể ngăn chặn việc thêm các dữ liệu không
mong muốn vào DataSet. Điều này cũng làm giảm các lỗi xảy ra trong quá trình cập
nhật dữ liệu từ DataSet vào cơ sở dữ liệu. Nếu người dùng cố ý thêm dữ liệu không thõa
các ràng buộc, chúng sẽ bị loại bỏ và trả về lỗi. Việc bắt các lỗi phát sinh và hiển thị
thông tin chi tiết giúp người dùng phát hiện ra các sai sót và có thể sửa đổi lại dữ liệu
trước khi thêm mới.
Bạn cũng cần phải định nghĩa một khóa chính để tìm kiếm, trích lọc hay sắp xếp các
DataRow trong DataTable.
Việc thêm các ràng buộc có thể làm giảm hiệu suất khi gọi phương thức Fill() của
DataAdapter vì các dòng nhận về từ cơ sở dữ liệu phải qua một bước kiểm tra các ràng
buộc trước khi được thêm vào DataSet. Do đó, bạn nên đặt giá trị thuộc tính
EnforceConstraints của DataSet là false trước khi gọi phương thức Fill() và gán lại giá
trị true sau khi gọi phương thức Fill().
Có hai cách để tạo ràng buộc cho các DataTable và DataColumn.
Thêm các ràng buộc bằng cách thiết lập giá trị cho các thuộc tính của DataTable
và DataColumn. Việc này giúp mã được thực thi nhanh hơn.
Gọi phương thức FillSchema() của đối tượng DataAdapter để sao chép thông tin
lược đồ cấu trúc cơ sở dữ liệu vào DataSet. Khi gọi phương thức này, giá trị các
thuộc tính của DataTable, DataColumn được thiết lập một cách tự động. Mặc dù
cách gọi đơn giản nhưng phương thức FillSchema() tốn nhiều thời gian để đọc
thông tin lược đồ cấu trúc từ cơ sở dữ liệu. Vì thế, nên tránh dùng cách này.
Trang 241
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"SELECT ProductID, ProductName " +
"FROM Products;" +
"SELECT OrderID " +
"FROM Orders;" +
"SELECT OrderID, ProductID, UnitPrice " +
"FROM [Order Details];";
mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Orders";
myDataSet.Tables["Table2"].TableName = "Order Details";
Khóa chính của bảng Products là cột ProductID, của bảng Orders là cột OrderID và
khóa chính của bảng Order Details được tạo từ hai cột OrderID, ProductID. Bạn phải
lấy tất cả các cột trong nhóm khóa chính của một bảng trong cơ sở dữ liệu nếu muốn tạo
ràng buộc khóa chính trên các cột của DataTable.
Tiếp theo, ta sẽ tìm hiểu cách để:
Thêm các ràng buộc vào các đối tượng Products DataTable, Orders DataTable và
Order Details DataTable.
Giới hạn miền giá trị của dữ liệu được gán cho các cột trong DataTable Products.
Trang 242
Giáo trình lập trình cơ sở dữ liệu
dùng làm khóa chính cho DataTable. Sở dĩ cần phải có một mảng vì trên thực tế, khóa
chính của một bảng trong cơ sở dữ liệu có thể tạo nên bởi nhiều cột.
Lưu ý:
Phương thức Fill() lấy tất cả các dòng từ một bảng trong cơ sở dữ liệu được quy định
bởi thuộc tính SelectCommand của đối tượng DataAdapter. Vì thế, nếu DataTable có
một khóa chính, việc gọi phương thức Fill() nhiều lần sẽ xảy ra trường hợp dòng dữ liệu
được thêm vào có giá trị trên cột khóa chính bị trùng với một dòng đã tồn tại trong
DataTable. Do đó dòng mới sẽ bị loại bỏ.
Nếu DataTable không chứa khóa chính, việc gọi phương thức Fill() nhiều lần dẫn
đến việc DataTable chứa nhiều dòng dữ liệu trùng nhau.
Ví dụ 1: Thêm khóa chính cho DataTable Products.
Để tạo khóa chính cho DataTable Products, đầu tiên, ta tạo một đối tượng DataTable
tên là productsDataTable và gán giá trị cho nó là bảng “Products” trong DataSet.
DataTable productsDataTable = myDataSet.Tables["Products"];
Tiếp theo, ta tạo một mảng đối tượng DataColumn tên là productsPrimaryKey để
chứa các cột khóa chính. Vì khóa chính của bảng Products trong cơ sở dữ liệu là
ProductID nên mảng này chỉ chứa một DataColumn (ProductID). Sau đó, mảng này
được gán cho thuộc tính PrimaryKey của productsDataTable.
DataColumn[] productsPrimaryKey =
new DataColumn[]
{
productsDataTable.Columns["ProductID"]
};
productsDataTable.PrimaryKey = productsPrimaryKey;
Khi thiết lập giá trị cho thuộc tính PrimaryKey của DataTable, các thuộc tính
AllowDBNull và Unique của DataColumn tự động được thay đổi như sau:
Thuộc tính AllowDBNull thay đổi thành false cho biết DataColumn không chấp
nhận giá trị null.
Thuộc tính Unique thay đổi thành true chỉ ra rằng giá trị tại cột đó (DataColumn)
trên mỗi hàng phải là duy nhất (khác nhau).
Trong ví dụ này, giá trị thuộc tính AllowDBNull và Unique của DataColumn
ProductID sẽ thay đổi thành các giá trị tương ứng là false và true.
Trang 243
Giáo trình lập trình cơ sở dữ liệu
Trong ví dụ này, việc gán giá trị cho thuộc tính PrimaryKey được viết chung bởi
một lệnh. Cách này ngắn gọn và súc tích hơn ví dụ trước. Ngoài ra, bạn cũng có thể
dùng phương thức Add() của thuộc tính Constraints để thêm một ràng buộc UNIQUE,
ràng buộc khóa chính hay khóa ngoại vào một DataTable. Phương thức này có nhiều
dạng:
// adds any constraint
void Add(Constraint myConstraint)
Ví dụ này cũng tương tự như ví dụ trước. Ở đây, ta tạo một ràng buộc khóa chính
bằng cách dùng phương thức Add() của thuộc tính Constraints. Đối số thứ 3 của phương
thức Add() cho biết đây là ràng buộc khóa chính.
Nếu có một cột trên đó có ràng buộc UNIQUE nhưng không phải là khóa chính, ta
sử dụng đối tượng UniqueConstraint để biểu diễn ràng buộc và thêm vào tập các ràng
buộc của DataTable.
UniqueConstraint myUC =
new UniqueConstraint(myDataTable.Columns["myColumn"]);
myDataTable.Constraints.Add(myUC);
Trang 244
Giáo trình lập trình cơ sở dữ liệu
myDataSet.Tables["Order Details"].PrimaryKey =
new DataColumn[]
{
myDataSet.Tables["Order Details"].Columns["OrderID"],
myDataSet.Tables["Order Details"].Columns["ProductID"]
};
Tạo khóa chính bằng cách dùng phương thức Add() của thuộc tính Constraints
myDataSet.Tables["Order Details"].Constraints.Add(
"Primary key constraint",
new DataColumn[]
{
myDataSet.Tables["Order Details"].Columns["OrderID"],
myDataSet.Tables["Order Details"].Columns["ProductID"]
},
true
);
Một điều cần nhớ khi thêm các ràng buộc vào DataTable là nó chỉ áp dụng cho dữ
liệu được lưu trong DataTable đó, hoàn toàn không có ảnh hưởng lên các dòng thực sự
của cơ sở dữ liệu.
Để hiểu rõ hơn vấn đề này, ta xét tình huống sau:
a. Bạn thêm một ràng buộc khóa chính vào DataTable
b. Lấy một số dòng từ một bảng của cơ sở dữ liệu và lưu vào DataTable
c. Thêm một dòng (DataRow) mới vào DataTable với giá trị khóa chính được lấy từ
dữ liệu đã tồn tại trong cơ sở dữ liệu nhưng không bị trùng với dữ liệu có sẵn
trong DataTable. Khi đó, DataRow mới sẽ được thêm vào DataTable mà không
hề xảy ra vấn đề gì. DataRow được thêm thành công vì DataTable chỉ biết đến
các dòng đã được lưu trong nó mà không cần biết đến các dòng được lưu trong
cơ sở dữ liệu nhưng chưa được lấy ở bước b.
d. Khi cập nhật DataRow mới từ DataSet vào cơ sở dữ liệu, ta sẽ gặp một lỗi
SqlException cho biết giá trị trên dòng mới vi phạm ràng buộc về khóa chính
trong cơ sở dữ liệu. Điều này hoàn toàn có thể xảy ra vì đã có một dòng trong
bảng của cơ sở dữ liệu đã sử dụng cùng giá trị trên cột khóa chính.
Trang 245
Giáo trình lập trình cơ sở dữ liệu
Đoạn mã sau minh họa cách thêm ràng buộc khóa ngoại từ cột OrderID của Order
Details DataTable tới cột OrderID của Orders DataTable.
ForeignKeyConstraint myFKC = new ForeignKeyConstraint(
myDataSet.Tables["Orders"].Columns["OrderID"],
myDataSet.Tables["Order Details"].Columns["OrderID"]
);
myDataSet.Tables["Order Details"].Constraints.Add(myFKC);
Lưu ý: DataColumn biểu diễn cột chứa khóa chính (OrderID của Orders) chứa trong
tham số thứ nhất còn DataColumn biểu diễn cột chứa khóa ngoại (OrderID của Order
Details) được quy định bởi tham số thứ hai. Ràng buộc được gán cho DataTable con
(Order Details).
Đoạn mã sau chỉ cách tạo ràng buộc khóa ngoại từ cột ProductID của Order Details
DataTable đến cột ProductID của Products DataTable.
myDataSet.Tables["Order Details"].Constraints.Add(
"Foreign key constraint to ProductID DataColumn " +
"of the Products DataTable",
myDataSet.Tables["Order Details"].Columns["ProductID"],
myDataSet.Tables["Products"].Columns["ProductID"]
);
DataColumn productIDDataColumn =
myDataSet.Tables["Products"].Columns["ProductID"];
productIDDataColumn.AllowDBNull = false;
productIDDataColumn.AutoIncrement = true;
productIDDataColumn.AutoIncrementSeed = -1;
productIDDataColumn.AutoIncrementStep = -1;
productIDDataColumn.ReadOnly = true;
productIDDataColumn.Unique = true;
Trang 246
Giáo trình lập trình cơ sở dữ liệu
Ví dụ tiếp theo gán giá trị 40 cho thuộc tính MaxLength của DataColumn có tên
ProductName. Điều này có nghĩa là bạn chỉ có thể gán một chuỗi có chiều dài không
quá 40 ký tự cho các giá trị trên cột ProductName.
myDataSet.Tables["Products"].Columns["ProductName"].MaxLength = 40;
Chương trình sau minh họa đầy đủ các ví dụ trong việc tạo ràng buộc cho các đối
tượng DataTable và DataColumn. Chương trình này cũng hiển thị giá trị các thuộc tính
CoumnName, DataType của các DataColumn trong mỗi DataTable. Thuộc tính
ColumnName chứa tên của DataColumn. Thuộc tính DataType chứa kiểu dữ liệu .Net
được dùng để biểu diễn giá trị của cột tương ứng được lưu trong DataColumn.
class AddRestrictions
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Orders";
Trang 247
Giáo trình lập trình cơ sở dữ liệu
DataColumn[] productsPrimaryKey =
new DataColumn[]
{
productsDataTable.Columns["ProductID"]
};
productsDataTable.PrimaryKey = productsPrimaryKey;
myDataSet.Tables["Orders"].PrimaryKey =
new DataColumn[]
{
myDataSet.Tables["Orders"].Columns["OrderID"]
};
myDataSet.Tables["Order Details"].Constraints.Add(
"Primary key constraint on the OrderID and ProductID columns",
new DataColumn[]
{
myDataSet.Tables["Order Details"].Columns["OrderID"],
myDataSet.Tables["Order Details"].Columns["ProductID"]
},
true
);
myDataSet.Tables["Order Details"].Constraints.Add(
"Foreign key constraint to ProductID DataColumn of the " +
"Products DataTable",
myDataSet.Tables["Products"].Columns["ProductID"],
myDataSet.Tables["Order Details"].Columns["ProductID"]
);
Trang 248
Giáo trình lập trình cơ sở dữ liệu
DataColumn productIDDataColumn =
myDataSet.Tables["Products"].Columns["ProductID"];
productIDDataColumn.AllowDBNull = false;
productIDDataColumn.AutoIncrement = true;
productIDDataColumn.AutoIncrementSeed = -1;
productIDDataColumn.AutoIncrementStep = -1;
productIDDataColumn.ReadOnly = true;
productIDDataColumn.Unique = true;
myDataSet.Tables["Products"].
Columns["ProductName"].MaxLength = 40;
Console.WriteLine("myDataColumn.DataType = " +
myDataColumn.DataType);
Console.WriteLine("myDataColumn.AllowDBNull = " +
myDataColumn.AllowDBNull);
Console.WriteLine("myDataColumn.AutoIncrement = " +
myDataColumn.AutoIncrement);
Console.WriteLine("myDataColumn.AutoIncrementSeed = " +
myDataColumn.AutoIncrementSeed);
Console.WriteLine("myDataColumn.AutoIncrementStep = " +
myDataColumn.AutoIncrementStep);
Console.WriteLine("myDataColumn.MaxLength = " +
myDataColumn.MaxLength);
Console.WriteLine("myDataColumn.ReadOnly = " +
myDataColumn.ReadOnly);
Trang 249
Giáo trình lập trình cơ sở dữ liệu
Console.WriteLine("myDataColumn.Unique = " +
myDataColumn.Unique);
}
}
}
}
myPrimaryKey = ProductID
myDataColumn.ColumnName = ProductID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = True
myDataColumn.AutoIncrementSeed = -1
myDataColumn.AutoIncrementStep = -1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = True
myDataColumn.Unique = True
myDataColumn.ColumnName = ProductName
myDataColumn.DataType = System.String
myDataColumn.AllowDBNull = True
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = 40
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
myPrimaryKey = OrderID
myDataColumn.ColumnName = OrderID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = True
myPrimaryKey = OrderID
myPrimaryKey = ProductID
myDataColumn.ColumnName = OrderID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
Trang 250
Giáo trình lập trình cơ sở dữ liệu
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
myDataColumn.ColumnName = ProductID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
myDataColumn.ColumnName = UnitPrice
myDataColumn.DataType = System.Decimal
myDataColumn.AllowDBNull = True
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
7.4.4. Tạo ràng buộc bởi phương thức FillSchema của DataAdapter
Thay vì tự tạo ra các ràng buộc bằng tay, ta có thể dùng phương thức FillSchema()
của đối tượng DataAdapter. Khi phương thức này được gọi, nó sẽ thực các công việc
sau:
Sao chép thông tin cấu trúc (schema) của cơ sở dữ liệu.
Tạo các đối tượng DataTable trong DataSet nếu chúng chưa tồn tại
Tạo các ràng buộc cho các DataTable
Gán giá trị thích hợp cho các thuộc tính của đối tượng DataColumn
Các thuộc tính của đối tượng DataColumn được gán giá trị bởi phương thức
FillSchema gồm:
ColumnName: tên của DataColumn
DataType: kiểu dữ liệu chuẩn .Net của giá trị được lưu trong DataColumn
MaxLength: số ký tự tối đa của một giá trị kiểu chuỗi
AllowDBNull: cho biết DataColumn có chấp nhận giá trị null hay không.
Unique: cho biết giá trị được lưu trên các dòng trong cùng DataColumn phải là
duy nhất
AutoIncrement, AutoIncrementSeed và AutoIncrementStep: Các thuộc tính quy
định giá trị bắt đầu, bước tăng cho các cột định danh.
Phương thức FillSchema() cũng tự động xác định DataColumn nào thuộc vào nhóm
khóa chính của DataTable và lưu chúng vào thuộc tính PrimaryKey của DataTable. Tuy
nhiên, phương thức này không tự động tạo các đối tượng ràng buộc khóa ngoại
Trang 251
Giáo trình lập trình cơ sở dữ liệu
(ForeignKeyConstraint) cho các DataTable. Nó không lấy các dòng trong cơ sở dữ liệu
mà chỉ lấy thông tin cấu trúc tạo nên cơ sở dữ liệu.
Phương thức FillSchema() được quá tải thành nhiều dạng. Dạng được dùng thông
dụng nhất là:
DataTable[] FillSchema(DataSet myDataSet, SchemaType mySchemaType)
Trong đó: mySchemaType chỉ ra cách mà bạn muốn xử lý khi ánh xạ các cấu trúc đã
tồn tại trong DataTable. Tham số này có kiểu enum System.Data.SchemaType.
CONSTANT DESCRIPTION
Mapped Áp dụng để ánh xạ cấu trúc các bảng đang tồn tại với lược đồ cấu
trúc lấy được từ cơ sở dữ liệu và cấu hình DataSet với lược đồ
chuyển đổi mới. Đây là giá trị nên được sử dụng.
Source Loại bỏ ánh xạ cấu trúc của các bảng và cấu hình DataSet mà
không cần chuyển đổi.
Bảng 7.7. Enum System.Data.SchemaType
Đoạn mã sau minh họa cách gọi phương thức FillSchema sử dụng hằng
SchemaType.Mapped để áp dụng cho các ánh xạ cấu trúc đến bảng đang tồn tại trong
DataSet. Phương thức FillSchema sao chép cấu trúc các bảng Products, Orders và Order
Details vào DataSet, gán giá trị thuộc tính PrimaryKey cho các DataTable và các thuộc
tính của DataColumn thích hợp.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT ProductID, ProductName " +
"FROM Products;" +
"SELECT OrderID " +
"FROM Orders;" +
"SELECT OrderID, ProductID, UnitPrice " +
"FROM [Order Details];";
mySqlDataAdapter.FillSchema(myDataSet, SchemaType.Mapped);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Orders";
myDataSet.Tables["Table2"].TableName = "Order Details";
Chương trình hoàn chỉnh sau minh họa cách gọi phương thức FillSchema() của đối
tượng DataAdapter để lấy cấu trúc của các bảng Products, Orders, Order Details và tự
động tạo ra các ràng buộc cho DataTable và DataColumn.
Chương trình 7.2: Sử dụng phương thức FillSchema để tạo ràng buộc
/*
Trang 252
Giáo trình lập trình cơ sở dữ liệu
class FillSchema
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlDataAdapter.FillSchema(myDataSet, SchemaType.Mapped);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Orders";
myDataSet.Tables["Table2"].TableName = "Order Details";
Trang 253
Giáo trình lập trình cơ sở dữ liệu
((UniqueConstraint)myConstraint).IsPrimaryKey);
Console.WriteLine("myDataColumn.DataType = " +
myDataColumn.DataType);
Console.WriteLine("myDataColumn.AllowDBNull = " +
myDataColumn.AllowDBNull);
Console.WriteLine("myDataColumn.AutoIncrement = " +
myDataColumn.AutoIncrement);
Console.WriteLine("myDataColumn.AutoIncrementSeed = " +
myDataColumn.AutoIncrementSeed);
Console.WriteLine("myDataColumn.AutoIncrementStep = " +
myDataColumn.AutoIncrementStep);
Console.WriteLine("myDataColumn.MaxLength = " +
myDataColumn.MaxLength);
Console.WriteLine("myDataColumn.ReadOnly = " +
myDataColumn.ReadOnly);
Console.WriteLine("myDataColumn.Unique = " +
myDataColumn.Unique);
}
}
}
}
myPrimaryKey = ProductID
myConstraint.IsPrimaryKey = True
myDataColumn.ColumnName = ProductID
myDataColumn.ColumnName = ProductID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = True
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
Trang 254
Giáo trình lập trình cơ sở dữ liệu
myDataColumn.ReadOnly = True
myDataColumn.Unique = True
myDataColumn.ColumnName = ProductName
myDataColumn.DataType = System.String
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = 40
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
myPrimaryKey = OrderID
myConstraint.IsPrimaryKey = True
myDataColumn.ColumnName = OrderID
myDataColumn.ColumnName = OrderID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = True
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = True
myDataColumn.Unique = True
myPrimaryKey = OrderID
myPrimaryKey = ProductID
myConstraint.IsPrimaryKey = True
myDataColumn.ColumnName = OrderID
myDataColumn.ColumnName = ProductID
myDataColumn.ColumnName = OrderID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
myDataColumn.ColumnName = ProductID
myDataColumn.DataType = System.Int32
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
myDataColumn.ColumnName = UnitPrice
Trang 255
Giáo trình lập trình cơ sở dữ liệu
myDataColumn.DataType = System.Decimal
myDataColumn.AllowDBNull = False
myDataColumn.AutoIncrement = False
myDataColumn.AutoIncrementSeed = 0
myDataColumn.AutoIncrementStep = 1
myDataColumn.MaxLength = -1
myDataColumn.ReadOnly = False
myDataColumn.Unique = False
7.5. Tìm kiếm, trích lọc và sắp xếp các dòng trong DataTable
Mỗi dòng trong DataTable được lưu bởi một đối tượng DataRow. Phần này trình
bày cách tìm kiếm, trích lọc và sắp xếp các DataRow trong một DataTable.
Đoạn mã sau minh họa cách cài đặt các bước a và b để thực thi lệnh truy vấn lấy 10
dòng đầu trong bảng Products. Sau đó gán DataColumn có tên ProductID cho thuộc tính
PrimaryKey.
mySqlDataAdapter.Fill(myDataSet, "Products");
mySqlConnection.Close();
productsDataTable.PrimaryKey =
new DataColumn[]
{
productsDataTable.Columns["ProductID"]
};
Tiếp theo, gọi phương thức Find() để lấy DataRow có giá trị trên cột ProductID là 3.
Phương thức Find() được gọi qua thuộc tính Rows của DataTable. Thuộc tính này có
kiểu DataRowCollection. Nếu khóa chính của một bảng trong cơ sở dữ liệu chứa hai
hay nhiều cột, tham số được gửi vào phương thức Find() phải là một mảng các đối
tượng.
Chẳng hạn, khóa chính của bảng Order Details gồm cột OrderID và ProductID. Giả
sử, ta đã thực hiện truy vấn và lấy dữ liệu từ bảng Order Details, lưu vào một DataTable
Trang 256
Giáo trình lập trình cơ sở dữ liệu
Trong đó:
filterExpression: điều kiện trích lọc
sortExpression: thứ tự sắp xếp kết quả trích lọc
myDataViewRowState: trạng thái của hàng được trích lọc. Tham số này có kiểu
enum System.Data.DataViewRowState.
CONSTANT DESCRIPTION
Added Dòng mới.
CurrentRows Các dòng hiện tại bao gồm các dòng Unchanged, Added, và cả
ModifiedCurrent.
Deleted Dòng đã bị xóa.
ModifiedCurrent Dòng hiện tại đã bị thay đổi.
ModifiedOriginal Dòng trước khi bị thay đổi.
None Không lọc bất kỳ dòng nào trong DataTable.
OriginalRows Các dòng ban đầu, bao gồm cả các dòng Unchanged và Deleted
Unchanged Dòng chưa bị thay đổi.
Bảng 7.8. Enum System.Data.DataViewRowState
Ví dụ:
// Lấy tất cả các dòng trong DataTable và không sắp xếp
DataRow[] productDataRows = productsDataTable.Select();
DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5");
Trang 257
Giáo trình lập trình cơ sở dữ liệu
DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5", "ProductID DESC");
DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5",
"ProductID DESC", DataViewRowState.OriginalRows);
Trong các ví dụ trên, biểu thức điều kiện lọc và sắp xếp đóng vai trò tương tự như
mệnh đề WHERE và ORDER BY trong câu lệnh SELECT. Các biểu thức này cung cấp
tính năng mạnh mẽ để dùng trong phương thức Sort() nhằm sắp xếp kết quả trích lọc bởi
phương thức Select().
Chẳng hạn, bạn có thể sử dụng các từ khóa AND, OR, NOT, IN, LIKE, các toàn tử
so sánh, toán tử số học, kí tự đại diện và cả các hàm tính toán ngay trong biểu thức điều
kiện trích lọc.
Đoạn mã sau minh họa cách sử dụng toán tử LIKE và ký tự đại diện % để tìm các
dòng có ProductName bắt đầu với ‘Cha’ đồng thời sắp xếp kết quả giảm theo
ProductID, nếu trùng, sắp tăng theo ProductName.
productDataRows =
productsDataTable.Select("ProductName LIKE 'Cha%'",
"ProductID DESC, ProductName ASC");
Chương trình hoàn chỉnh dưới đây minh họa cách sử dụng các phương thức Find,
Select, Sort và các toán tử để thực hiện tìm kiếm, trích lọc và sắp xếp các DataRow
trong một DataTable.
Chương trình 7.3: Tìm kiếm, trích lọc và sắp xếp DataRow trong DataTable
/*
FindFilterAndSortDataRows.cs illustrates how to find, filter,
and sort DataRow objects
*/
using System;
using System.Data;
using System.Data.SqlClient;
class FindFilterAndSortDataRows
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
Trang 258
Giáo trình lập trình cơ sở dữ liệu
mySqlCommand.CommandText =
"SELECT TOP 10 ProductID, ProductName " +
"FROM Products " +
"ORDER BY ProductID;" +
"SELECT TOP 10 OrderID, ProductID, UnitPrice, Quantity " +
"FROM [Order Details] " +
"ORDER BY OrderID";
mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Order Details";
orderDetailsDataTable.Constraints.Add(
"Primary key constraint on the OrderID and ProductID columns",
new DataColumn[]
{
orderDetailsDataTable.Columns["OrderID"],
orderDetailsDataTable.Columns["ProductID"]
},
true
);
Trang 259
Giáo trình lập trình cơ sở dữ liệu
object[] orderDetails =
new object[]
{
10248,
11
};
DataRow orderDetailDataRow =
orderDetailsDataTable.Rows.Find(orderDetails);
DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5", "ProductID DESC",
DataViewRowState.OriginalRows);
productDataRows =
productsDataTable.Select("ProductName LIKE 'Cha*'",
"ProductID ASC, ProductName DESC");
Trang 260
Giáo trình lập trình cơ sở dữ liệu
Lệnh SELECT sẽ được thực thi khi phương thức Fill() của DataAdapter được gọi và
đưa các kết quả trả về từ bảng Customers vào một DataSet.
Trang 261
Giáo trình lập trình cơ sở dữ liệu
Tương tự, ta cũng có thể thêm, cập nhật và xóa các DataRow khỏi một DataTable
trong DataSet. Sau đó, cập nhật các thay đổi vào cơ sở dữ liệu bằng cách sử dụng đối
tượng DataAdapter.
7.6.1. Cấu hình DataAdapter để cập nhật thay đổi vào cơ sở dữ liệu
Trước khi cập nhật các thay đổi vào cơ sở dữ liệu, đầu tiên, ta phải thiết lập các đối
tượng Command chứa các lệnh SQL INSERT, UPDATE và DELETE thích hợp. Các
đối tượng Command này được lưu trong các thuộc tính InsertCommand,
UpdateCommand và DeleteCommand của đối tượng DataAdapter.
Các thay đổi trong DataSet được cập nhật vào cơ sở dữ liệu bằng cách gọi phương
thức Update() của DataAdapter. Khi thêm, cập nhật hay xóa các đối tượng DataRow từ
DataSet rồi gọi phương thức Update, đối tượng Command tương ứng (InsertCommand,
UpdateCommand hoặc DeleteCommand) sẽ được thực thi để đưa các thay đổi đó vào cơ
sở dữ liệu.
7.6.2. Thiết lập thuộc tính InsertCommand
Đoạn mã sau tạo một đối tượng SqlCommand có tên myInsertCommand chứa một
lệnh truy vấn SQL INSERT và gán nó cho thuộc tính InsertCommand của DataAdapter.
SqlCommand myInsertCommand = mySqlConnection.CreateCommand();
myInsertCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName, Address" +
") VALUES (" +
" @CustomerID, @CompanyName, @Address" +
")";
myInsertCommand.Parameters.Add("@CustomerID",
SqlDbType.NChar, 5, "CustomerID");
myInsertCommand.Parameters.Add("@CompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
myInsertCommand.Parameters.Add("@Address",
SqlDbType.NVarChar, 60, "Address");
mySqlDataAdapter.InsertCommand = myInsertCommand;
Trang 262
Giáo trình lập trình cơ sở dữ liệu
myUpdateCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = @NewCompanyName, " +
" Address = @NewAddress " +
"WHERE CustomerID = @OldCustomerID " +
"AND CompanyName = @OldCompanyName " +
"AND Address = @OldAddress";
myUpdateCommand.Parameters.Add("@NewCompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
myUpdateCommand.Parameters.Add("@NewAddress",
SqlDbType.NVarChar, 60, "Address");
myUpdateCommand.Parameters.Add("@OldCustomerID",
SqlDbType.NChar, 5, "CustomerID");
myUpdateCommand.Parameters.Add("@OldCompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
myUpdateCommand.Parameters.Add("@OldAddress",
SqlDbType.NVarChar, 60, "Address");
myUpdateCommand.Parameters["@OldCustomerID"].SourceVersion =
DataRowVersion.Original;
myUpdateCommand.Parameters["@OldCompanyName"].SourceVersion =
DataRowVersion.Original;
myUpdateCommand.Parameters["@OldAddress"].SourceVersion =
DataRowVersion.Original;
mySqlDataAdapter.UpdateCommand = myUpdateCommand;
Trang 263
Giáo trình lập trình cơ sở dữ liệu
“Last One Wins” Concurrency: với hình thức này, bạn luôn có thể thay đổi dữ
liệu trên một dòng và các thay đổi này sẽ ghi đè lên dữ liệu mà người khác đã
thay đổi trước đó. Nên tránh sử dụng hình thức này.
Để sử dụng tính đồng thời tối ưu cho các lệnh UPDATE hay DELETE, bạn phải
thực hiện những việc sau trong mệnh đề WHERE
a. Đưa tất cả các cột được sử dụng trong câu lệnh SELECT ban đầu vào sau mệnh
đề WHERE.
b. Gán giá trị các cột bằng với giá trị ban đầu nhận được từ dòng trong bảng trước
khi thay đổi giá trị.
Mục đích của việc này là buộc lệnh UPDATE hay DELETE phải kiểm tra xem các
dòng mà bạn muốn cập nhật vẫn chưa bị thay đổi. Theo đó, bạn có thể bảo đảm các thay
đổi của mình sẽ không ghi đè lên những thay đổi của người khác. Dĩ nhiên, các dòng
ban đâu đã bị xóa bởi một người dùng khác thì các lệnh UPDATE hay DELETE sẽ
không có tác dụng và gây ra lỗi.
Nếu sử dụng hình thức “Last One Wins”, chỉ cần đặt khóa chính và giá trị của nó sau
mệnh đề WHERE của lệnh UPDATE hay DELETE. Vì lệnh UPDATE không kiểm tra
các giá trị ban đầu nên nó sẽ ghi đè dữ liệu lên các thay đổi của người khác nếu nó còn
tồn tại. Tương tự, lệnh DELETE sẽ xóa dòng thỏa điều kiện cho dù có người khác đã
cập nhật hay không.
Quay trở lại với ví dụ trước, thuộc tính UpdateCommand của DataAdapter chứa lệnh
UPDATE với mệnh đề WHERE có đủ tất cả các cột. Như vậy, nó thỏa yêu cầu về tính
đồng thời tối ưu.
Yêu cầu thứ hai là bạn phải gán giá trị ban đầu cho các cột sau mệnh đề WHERE.
Việc này được thực hiện bằng cách thiết lập giá trị thuộc tính SourceVersion của các
tham số @OldCustomerID, @OldCompanyName, @OldAddress là DataRowVersion.
Original. Lúc chạy chương trình, giá trị ban đầu từ các DataColumn trong DataRow
trước khi bạn thay đổi chúng được truyền cho tham số sau mệnh đề WHERE của lệnh
UPDATE.
CONSTANT DESCRIPTION
Current Giá trị hiện tại của cột.
Default Giá trị mặc định của cột.
Original Giá trị ban đầu của cột.
Proposed Giá trị cụ thể được gán khi bạn thay đổi một DataRow bằng cách
gọi phương thức BeginEdit().
Bảng 7.9. Enum System.Data.DataRowVersion
Trang 264
Giáo trình lập trình cơ sở dữ liệu
myDeleteCommand.Parameters.Add("@OldCustomerID",
SqlDbType.NChar, 5, "CustomerID");
myDeleteCommand.Parameters.Add("@OldCompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
myDeleteCommand.Parameters.Add("@OldAddress",
SqlDbType.NVarChar, 60, "Address");
myDeleteCommand.Parameters["@OldCustomerID"].SourceVersion =
DataRowVersion.Original;
myDeleteCommand.Parameters["@OldCompanyName"].SourceVersion =
DataRowVersion.Original;
myDeleteCommand.Parameters["@OldAddress"].SourceVersion =
DataRowVersion.Original;
mySqlDataAdapter.DeleteCommand = myDeleteCommand;
Console.WriteLine("myNewDataRow.RowState = " +
myNewDataRow.RowState);
Trang 265
Giáo trình lập trình cơ sở dữ liệu
myNewDataRow["CustomerID"] = "J5COM";
myNewDataRow["CompanyName"] = "J5 Company";
myNewDataRow["Address"] = "1 Main Street";
myDataTable.Rows.Add(myNewDataRow);
Console.WriteLine("myNewDataRow.RowState = " +
myNewDataRow.RowState);
mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
Console.WriteLine("myNewDataRow.RowState = " +
myNewDataRow.RowState);
DisplayDataRow(myNewDataRow, myDataTable);
}
public static void DisplayDataRow( DataRow myDataRow,
DataTable myDataTable)
{
Console.WriteLine("\nIn DisplayDataRow()");
foreach (DataColumn myDataColumn in myDataTable.Columns)
{
Console.WriteLine(myDataColumn + "= " +
myDataRow[myDataColumn]);
}
}
Phương thức Update() của đối tượng DataAdapter có các dạng sau
int Update(DataRow[] myDataRows)
int Update(DataSet myDataSet)
int Update(DataTable myDataTable)
int Update(DataRow[] myDataRows, DataTableMapping myDataTableMapping)
int Update(DataSet myDataSet, string dataTableName)
Trong đó, dataTableName là tên của DataTable cần được cập nhật. Phương thức
Update() trả về một số nguyên cho biết số dòng được cập nhật vào cơ sở dữ liệu thành
công.
Trong phương thức AddDataRow() ở trên có dùng thuộc tính RowState của đối
tượng DataRow. Thuộc tính này có kiểu enumSystem.Data.DataViewRowState. Giá trị
Trang 266
Giáo trình lập trình cơ sở dữ liệu
của thuộc tính này được xuất ra màn hình nhiều lần để cho thấy sự thay đổi trạng thái
của DataRow trong DataTable.
CONSTANT DESCRIPTION
Added The DataRow has been added to the DataRowCollection of the
DataTable.
Deleted The DataRow has been removed from the DataTable.
Detached The DataRow isn't part of the DataTable.
Modified The DataRow has been modified.
Unchanged The DataRow hasn't been modified.
Bảng 7.10. Enum System.Data.DataViewRowState
Calling myDataTable.Rows.Add()
myNewDataRow.RowState = Added
Calling mySqlDataAdapter.Update()
numOfRows = 1
myNewDataRow.RowState = Unchanged
In DisplayDataRow()
CustomerID = J5COM
CompanyName = J5 Company
Address = 1 Main Street
Trang 267
Giáo trình lập trình cơ sở dữ liệu
Ví dụ:
Phương thức ModifyDataRow() sau đây minh họa các bước để cập nhật dòng được
thêm vào trong hàm AddDataRow() của ví dụ trước.
Console.WriteLine("myEditDataRow.RowState = " +
myEditDataRow.RowState);
mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
Trang 268
Giáo trình lập trình cơ sở dữ liệu
Console.WriteLine("myEditDataRow.RowState = " +
myEditDataRow.RowState);
DisplayDataRow(myEditDataRow, myDataTable);
}
Calling mySqlDataAdapter.Update()
numOfRows = 1
myEditDataRow.RowState = Unchanged
In DisplayDataRow()
CustomerID = J5COM
CompanyName = Widgets Inc.
Address = 1 Any Street
Ta cũng có thể dùng phương thức BeginEdit() để đánh dấu điểm bắt đầu thực hiện
việc cập nhật dữ liệu trên một DataRow. Sau đó, sử dụng phương thức EndEdit() để kết
thúc việc cập nhật và xác nhận các thay đổi hoặc CancelEdit() để xóa bỏ các thay đổi và
trả lại giá trị trước khi bắt đầu cập nhật cho DataRow.
myEditDataRow.BeginEdit();
myEditDataRow.EndEdit();
Trang 269
Giáo trình lập trình cơ sở dữ liệu
new DataColumn[]
{
myDataTable.Columns["CustomerID"]
};
myRemoveDataRow.Delete();
Console.WriteLine("myRemoveDataRow.RowState = " +
myRemoveDataRow.RowState);
mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
Console.WriteLine("myRemoveDataRow.RowState = " +
myRemoveDataRow.RowState);
}
Calling mySqlDataAdapter.Update()
numOfRows = 1
myRemoveDataRow.RowState = Detached
7.7. Lấy giá trị mới của các cột định danh
Cột định danh (identity) là cột mà giá trị của nó được tự động gán (và tăng lên) khi
có một dòng mới được thêm vào.
Chẳng hạn, cột ProductID của bảng Products là một cột định danh. Trong phần này,
ta sẽ tìm hiểu cách thêm một dòng mới vào bảng Products và lấy giá trị mới được sinh
ra trên cột ProductID.
Giả sử, ta có một DataTable tên là productsDataTable nhận dữ liệu từ lệnh SELECT
SELECT
ProductID, ProductName, UnitPrice
FROM Products
ORDER BY ProductID
Đoạn mã sau thiết lập thuộc tính PrimaryKey (ProductID) cho DataTable:
Trang 270
Giáo trình lập trình cơ sở dữ liệu
productsDataTable.PrimaryKey =
new DataColumn[]
{
productsDataTable.Columns["ProductID"]
};
DataColumn productIDDataColumn =
productsDataTable.Columns["ProductID"];
productIDDataColumn.AllowDBNull = false;
productIDDataColumn.AutoIncrement = true;
productIDDataColumn.AutoIncrementSeed = -1;
productIDDataColumn.AutoIncrementStep = -1;
productIDDataColumn.ReadOnly = true;
productIDDataColumn.Unique = true;
Với các thiết lập này, khi một DataRow mới được thêm vào DataTable, giá trị trên
cột ProductID của của dòng mới được khởi tạo là -1.
Gán các đối tượng Command thích hợp cho các thuộc tính InsertCommand,
UpdateCommand và DeleteCommand của DataAdapter. Lệnh truy vấn SQL tương ứng
với từng Command như sau:
myUpdateCommand.CommandText =
"UPDATE Products " +
"SET ProductName = @NewProductName, " +
"UnitPrice = @NewUnitPrice " +
"WHERE ProductID = @OldProductID " +
"AND ProductName = @OldProductName " +
"AND UnitPrice = @OldUnitPrice";
myDeleteCommand.CommandText =
"DELETE FROM Products " +
"WHERE ProductID = @OldProductID " +
"AND ProductName = @OldProductName " +
"AND UnitPrice = @OldUnitPrice";
myInsertCommand.CommandText =
"INSERT INTO Products ( ProductName, UnitPrice )" +
"VALUES (@MyProductName, @MyUnitPrice);" +
"SELECT @MyProductID = SCOPE_IDENTITY();";
myInsertCommand.Parameters.Add(
"@MyProductName", SqlDbType.NVarChar, 40, "ProductName");
myInsertCommand.Parameters.Add(
"@MyUnitPrice", SqlDbType.Money, 0, "UnitPrice");
myInsertCommand.Parameters.Add(
"@MyProductID", SqlDbType.Int, 0, "ProductID");
myInsertCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
Trong lệnh SELECT, để lấy giá trị mới trên cột ProductID, ta gọi hàm
SCOPE_IDENTITY() của SQL Server. Hàm này trả về giá trị định danh được chèn vào
Trang 271
Giáo trình lập trình cơ sở dữ liệu
sau cùng trong một bảng bất kỳ được thực hiện trong phiên làm việc hiện hành và
Stored Procedure, Trigger, Function hay Batch của cơ sở dữ liệu.
Khi thêm một DataRow mới vào DataTable, ProductID DataColumn của DataRow
được khởi gán là -1. Khi gọi phương thức Update() của đối tượng DataAdapter để thêm
dòng mới vào cơ sở dữ liệu, những việc sau sẽ xảy ra:
- DataRow mới được lưu vào cơ sở dữ liệu bằng lệnh INSERT lưu trong thuộc
tính InsertCommand. Cột ProductID của bảng Products được tự động gán một
giá trị mới bởi cơ sở dữ liệu.
- Giá trị của cột ProductID được lấy về từ lệnh SELECT trong thuộc tính
InsertCommand.
- ProductID DataColumn trong DataRow được gán giá trị mới nhận về.
Dưới đây là chi tiết các thủ tục được viết trong SQL Server
/*
AddProduct4.sql creates a procedure that adds a row to the
Products table using values passed as parameters to the
procedure. The procedure returns the ProductID of the new row
using a RETURN statement
*/
Trang 272
Giáo trình lập trình cơ sở dữ liệu
@MyProductName nvarchar(40),
@MyUnitPrice money
AS
RETURN @MyProductID
/*
UpdateProduct.sql creates a procedure that modifies a row
in the Products table using values passed as parameters
to the procedure
*/
/*
DeleteProduct.sql creates a procedure that removes a row
from the Products table
*/
Trang 273
Giáo trình lập trình cơ sở dữ liệu
AS
/*
DeleteProduct2.sql creates a procedure that removes a row
from the Products table
*/
CREATE PROCEDURE DeleteProduct2
@OldProductID int,
@OldProductName nvarchar(40),
@OldUnitPrice money
AS
Trang 274
Giáo trình lập trình cơ sở dữ liệu
Chẳng hạn, thủ tục DeleteProduct() còn phải thực hiện một lệnh INSERT để thêm
một dòng vào bảng ProductAudit (của cơ sở dữ liệu Northwind) để ghi nhận lại việc xóa
một dòng khỏi bảng Products. Trong trường hợp này, bạn phải sử dụng lệnh SET
NOCOUNT ON trước khi thực hiện lệnh INSERT. (xem Stored Procedure
DeleteProduct2 ở trên)
Bằng cách dùng SET NOCOUNT ON trước lệnh INSERT, chỉ số dòng bị ảnh hưởng
bởi lệnh DELETE mới được trả về. Ngoài SET NOCOUNT ON, T-SQL cũng cung cấp
lệnh SET NOCOUNT OFF cho phép sử dụng để thực hiện các lệnh INSERT, UPDATE
hay DELETE trước lệnh SQL chính.
Trước khi cập nhật các thay đổi vào cơ sở dữ liệu, ta phải gán giá trị cho các thuộc
tính InsertCommand, UpdateCommand và DeleteCommand của đối tượng DataAdapter
bằng các đối tượng Command. Mỗi đối tượng Command này chứa tên một Stored
Procedure (AddProduct5, UpdateProduct và DeleteProduct). Khi thêm, cập nhật hay
xóa một DataRow khỏi DataSet rồi gọi phương thức Update() của đối tượng
DataAdapter, Stored Procedure thích hợp sẽ được thực thi để lưu các thay đổi vào cơ sở
dữ liệu.
Đoạn mã sau minh họa cách tạo các đối tượng Command cần thiết
// ============================================
// Tạo đối tượng SqlCommand chứa thủ tục AddProduct4()
SqlCommand myInsertCommand = mySqlConnection.CreateCommand();
myInsertCommand.CommandText =
"EXECUTE @MyProductID = AddProduct4 @MyProductName, @MyUnitPrice";
myInsertCommand.Parameters.Add(
"@MyProductID", SqlDbType.Int, 0, "ProductID");
myInsertCommand.Parameters["@MyProductID"].Direction =
ParameterDirection.Output;
myInsertCommand.Parameters.Add(
"@MyProductName", SqlDbType.NVarChar, 40, "ProductName");
Trang 275
Giáo trình lập trình cơ sở dữ liệu
myInsertCommand.Parameters.Add(
"@MyUnitPrice", SqlDbType.Money, 0, "UnitPrice");
mySqlDataAdapter.InsertCommand = myInsertCommand;
// ============================================
// Tạo đối tượng SqlCommand chứa thủ tục UpdateProduct
myUpdateCommand.Parameters.Add(
"@OldProductID", SqlDbType.Int, 0, "ProductID");
myUpdateCommand.Parameters.Add(
"@NewProductName", SqlDbType.NVarChar, 40, "ProductName");
myUpdateCommand.Parameters.Add(
"@NewUnitPrice", SqlDbType.Money, 0, "UnitPrice");
myUpdateCommand.Parameters.Add(
"@OldProductName", SqlDbType.NVarChar, 40, "ProductName");
myUpdateCommand.Parameters.Add(
"@OldUnitPrice", SqlDbType.Money, 0, "UnitPrice");
myUpdateCommand.Parameters["@OldProductID"].SourceVersion =
DataRowVersion.Original;
myUpdateCommand.Parameters["@OldProductName"].SourceVersion =
DataRowVersion.Original;
myUpdateCommand.Parameters["@OldUnitPrice"].SourceVersion =
DataRowVersion.Original;
mySqlDataAdapter.UpdateCommand = myUpdateCommand;
// ============================================
// Tạo đối tượng SqlCommand chứa thủ tục DeleteProduct
myDeleteCommand.Parameters.Add(
"@OldProductID", SqlDbType.Int, 0, "ProductID");
myDeleteCommand.Parameters.Add(
"@OldProductName", SqlDbType.NVarChar, 40, "ProductName");
myDeleteCommand.Parameters.Add(
"@OldUnitPrice", SqlDbType.Money, 0, "UnitPrice");
myDeleteCommand.Parameters["@OldProductID"].SourceVersion =
DataRowVersion.Original;
myDeleteCommand.Parameters["@OldProductName"].SourceVersion =
DataRowVersion.Original;
myDeleteCommand.Parameters["@OldUnitPrice"].SourceVersion =
DataRowVersion.Original;
mySqlDataAdapter.DeleteCommand = myDeleteCommand;
Trang 276
Giáo trình lập trình cơ sở dữ liệu
Để thêm một dòng mới vào DataTable, ta thực hiện theo 4 bước đã được trình bày
trong phần Cập nhật dữ liệu trên các dòng của DataTable. Phương thức AddDataRow()
sau minh họa chi tiết 4 bước để thêm một dòng mới vào DataTable.
public static int AddDataRow( DataTable myDataTable,
SqlDataAdapter mySqlDataAdapter, SqlConnection mySqlConnection )
{
// step 1: use the NewRow() method of the DataTable to
// create a new DataRow
myNewDataRow["ProductName"] = "Widget";
myNewDataRow["UnitPrice"] = 10.99;
myDataTable.Rows.Add(myNewDataRow);
Trang 277
Giáo trình lập trình cơ sở dữ liệu
myDataTable.PrimaryKey =
new DataColumn[]
{
myDataTable.Columns["ProductID"]
};
mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
}
myRemoveDataRow.Delete();
mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
Trang 278
Giáo trình lập trình cơ sở dữ liệu
Kết quả của các phương thức trên cũng giống như kết quả của các phương thức được
dùng để thêm, cập nhật và xóa DataRow bằng cách sử dụng các lệnh SQL trực tiếp như
đã trình bày ở mục Cập nhật dữ liệu trên các dòng của DataTable.
Có 3 lớp CommandBuilder, tất cả đều thuộc nhóm lớp kết nối: SqlCommandBuilder,
OleDbCommandBuilder và OdbcCommandBuilder.
Để CommandBuilder có thể tự sinh ra các lệnh SQL, trước hết, phải gán giá trị cho
thuộc tính SelectCommand của DataAdapter. Lệnh SELECT được dùng trong
Command này phải nhận dữ liệu từ một bảng duy nhất.
Ví dụ sau minh họa cách lấy dữ liệu từ bảng Customers.
SqlCommand mySelectCommand = mySqlConnection.CreateCommand();
mySelectCommand.CommandText =
"SELECT CustomerID, CompanyName, Address " +
"FROM Customers " +
"ORDER BY CustomerID";
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();
mySqlDataAdapter.SelectCommand = mySelectCommand;
Tiếp theo, tạo một đối tượng CommandBuilder và gửi đối tượng DataAdapter qua
tham số của phương thức khởi tạo.
SqlCommandBuilder mySqlCommandBuilder =
Trang 279
Giáo trình lập trình cơ sở dữ liệu
new SqlCommandBuilder(mySqlDataAdapter);
Để lấy các lệnh được sinh tự động, ta dùng các phương thức GetInsertCommand(),
GetUpdateCommand() và GetDeleteCommand của CommandBuilder.
Ví dụ sau xuất các lệnh truy vấn SQL được tạo tự động bởi CommandBuilder
Console.WriteLine(
"mySqlCommandBuilder.GetInsertCommand().CommandText =\n" +
mySqlCommandBuilder.GetInsertCommand().CommandText );
Console.WriteLine(
"mySqlCommandBuilder.GetUpdateCommand().CommandText =\n" +
mySqlCommandBuilder.GetUpdateCommand().CommandText );
Console.WriteLine(
"mySqlCommandBuilder.GetDeleteCommand().CommandText =\n" +
mySqlCommandBuilder.GetDeleteCommand().CommandText );
// Hàm xử lý sự kiện
public static void FillErrorEventHandler(
object sender, FillErrorEventArgs myFEEA )
Trang 280
Giáo trình lập trình cơ sở dữ liệu
{
if (myFEEA.Errors.GetType() == typeof(System.OverflowException))
{
Console.WriteLine("A loss of precision occurred");
myFEEA.Continue = true;
}
}
Tham số thứ hai của hàm xử lý sự kiện RowUpdating của đối tượng SqlDataAdapter
có kiểu SqlRowUpdatingEventArgs. Bảng sau liệt kê một số thuộc tính của lớp này và ý
nghĩa của chúng.
PROPERTY TYPE DESCRIPTION
Command SqlCommand Thuộc tính đọc và ghi. Lấy hay gán đối tượng
SqlCommand được thực thi khi phương thức
Update() được gọi.
Trang 281
Giáo trình lập trình cơ sở dữ liệu
Delete
Insert
Select
Update
Status UpdateStatus Thuộc tính đọc và ghi. Lấy hay gán trạng thái
của đối tượng Command. Thuộc tính này có kiểu
enum UpdateStatus được định nghĩa trong
namespace System.Data và nhận một trong các giá
trị sau:
Ví dụ sau minh họa một hàm xử lý sự kiện RowUpdating nhằm chặn tất cả các dòng
có giá trị trên cột CustomerID là “J5COM” và đang được thêm vào cơ sở dữ liệu.
Trang 282
Giáo trình lập trình cơ sở dữ liệu
mySRUEA.Status = UpdateStatus.SkipCurrentRow;
}
}
Trang 283
Giáo trình lập trình cơ sở dữ liệu
customersDataTable.ColumnChanged +=
new DataColumnChangeEventHandler(ColumnChangedEventHandler);
Trang 284
Giáo trình lập trình cơ sở dữ liệu
{
Console.WriteLine("myDCCEA.Column = " + myDCCEA.Column);
Console.WriteLine("myDCCEA.ProposedValue = " +
myDCCEA.ProposedValue);
}
Row DataRow Thuộc tính chỉ đọc. Lấy DataRow chứa các DataColumn
có giá trị bị thay đổi.
Bảng 7.17. Các thuộc tính của DataRowChangeEventArgs
Ví dụ:
Đoạn mã sau cho thấy hai phương thức xử lý sự kiện RowChanging và
RowChanged. Các phương thức này sẽ lấy giá trị thuộc tính Action và xuất ra màn hình.
customersDataTable.RowChanged +=
new DataRowChangeEventHandler(RowChangedEventHandler);
Trang 285
Giáo trình lập trình cơ sở dữ liệu
{
Console.WriteLine("myDRCEA.Action = " + myDRCEA.Action);
}
customersDataTable.RowDeleted +=
new DataRowChangeEventHandler(RowDeletedEventHandler);
UPDATE Customers
SET
CompanyName = @NewCompanyName,
Address = @NewAddress
WHERE CustomerID = @OldCustomerID
AND CompanyName = @OldCompanyName
AND Address = @OldAddress
Trang 286
Giáo trình lập trình cơ sở dữ liệu
Lệnh UPDATE này sử dụng tính đồng thời tối ưu (optimistic concurency) vì các cột
được cập nhật cũng được kiểm tra bởi mệnh đề WHERE.
Tại sao việc cập nhật tại bước 4 lại xảy ra lỗi? Lý do là sự ảnh hưởng của tính đồng
thời tối ưu. Vì cột CompanyName được sử dụng sau mệnh đề WHERE của lệnh
UPDATE, dòng ban đầu được tải bởi User 2 không được tìm thấy do User 1 đã thay đổi
nó. Vì thế, lệnh UPDATE thực hiện không thành công.
Đây là khó khăn chính khi sử dụng tính đồng thời tối ưu. Để giải quyết vấn đề này,
ta gán giá trị true cho thuộc tính ContinueUpdateOnError của đối tượng DataAdapter.
Việc này cho phép DataAdapter tiếp tục thực thiện việc cập nhật bất cứ DataRow nào
ngay cả khi có lỗi xảy ra. Theo đó, User 2 có thể tiếp tục lưu các dòng không gây ra lỗi.
Ví dụ:
Để gán giá trị cho thuộc tính ContinueUpdateOnError của DataAdapter, ta viết
mySqlDataAdapter.ContinueUpdateOnError = true;
Khi phương thức Update() của DataAdapter được gọi, nó lưu tất cả các thay đổi
không gây ra lỗi vào cơ sở dữ liệu. Sau đó, bạn có thể kiểm tra các lỗi bằng cách dùng
thuộc tính HasErrors của đối tượng DataSet hoặc DataTable.
Trang 287
Giáo trình lập trình cơ sở dữ liệu
} // End If DataRow
} // End For Each DataRow
} // End If DataTable
} // End For Each DataTable
}
Giá trị của biến numOfRows sẽ không phải là 1 (như bạn nghĩ) mà là tổng số dòng
có trong bảng hay số dòng lấy được bởi lệnh SELECT. Lý do là phương thức Fill() luôn
lấy tất cả cách dòng thõa điều kiện sau mệnh đề WHERE của lệnh SELECT để đưa vào
DataSet. Bỏ qua bất kỳ các dòng nào (lấy từ cơ sở dữ liệu) có giá trị trên cột khóa chính
trùng với giá trị trong cột tương ứng của DataTable.
Nếu người dùng muốn cập nhật một dòng đã bị xóa khỏi cơ sở dữ liệu, cách duy
nhất bạn có thể là làm tươi lại các dòng và buộc người dùng nhập lại dòng đó.
Trang 288
Giáo trình lập trình cơ sở dữ liệu
dịch. Để biểu diễn một giao dịch, ta dùng đối tượng Transaction (được trình bày trong
chương 4).
Như đã biết, DataSet không kết nối trực tiếp tới cơ sở dữ liệu. Thay vào đó, ta dùng
phương thức Fill() và Update() của DataAdapter để lấy và cập nhật các dòng giữa
DataSet và cơ sở dữ liệu. Thực tế, DataSet không hề biết gì về cơ sở dữ liệu. Một đối
tượng DataSet đơn thuần chỉ dùng để lưu trữ bản sao của dữ liệu trong trạng thái kết nối
đã bị ngắt. Do đó, DataSet không có chức năng để xử lý các giao dịch.
Vậy DataSet sử dụng các giao dịch bằng cách nào? Câu trả lời là phải sử dụng thuộc
tính Transaction của các đối tượng Command trong DataAdapter.
SqlTransaction mySqlTransaction =
mySqlConnection.BeginTransaction();
mySqlDataAdapter.SelectCommand.Transaction = mySqlTransaction;
mySqlDataAdapter.InsertCommand.Transaction = mySqlTransaction;
mySqlDataAdapter.UpdateCommand.Transaction = mySqlTransaction;
mySqlDataAdapter.DeleteCommand.Transaction = mySqlTransaction;
mySqlDataAdapter.Update(myDataSet);
Cuối cùng, gọi phương thức Commit() hoặc Rollback() của đối tượng Transaction để
xác nhận hoặc hủy bỏ giao dịch.
mySqlTransaction.Commit(); // or mySqlTransaction.Rollback();
Mặc định, một giao dịch sẽ bị hủy (rollback) nếu bạn không gọi phương thức
Commit() để xác nhận các thay đổi.
Ví dụ:
Giả sử, bạn đã tạo một DataSet có định kiểu với tên MyDataSet để lưu trữ thông tin
lấy được từ bảng Customers. Khi đó, để đọc giá trị trên cột CustomerID của một
DataRow, ta viết myDataRow.CustomerID thay cho cách viết thông thường
myDataRow[“CustomerID”].
Một đối tượng được tạo từ lớp MyDataSet cũng tạo ra các phương thức
NewCustomersRow(), AddCustomersRow() và RemoveCustomersRow() cho phép tạo,
thêm một đối tượng Customer mới và xóa một Customer. Phương thức
FindByCustomerID() cho phép tìm một đối tượng Customer theo CustomerID. Hoặc
cũng có thể kiểm tra giá trị null, gán giá trị null cho một cột nào đó, chẳng hạn
IsContactNameNull(), SetContactNameNull().
Phương thức Form1_Load() sau minh họa chi tiết các bước để thêm, cập nhật và xóa
một dòng trong DataSet có định kiểu.
private void Form1_Load(object sender, System.EventArgs e)
{
// populate the DataSet with the CustomerID, CompanyName,
// and Address columns from the Customers table
sqlConnection1.Open();
sqlDataAdapter1.Fill(myDataSet1, "Customers");
MyDataSet.CustomersDataTable myDataTable =
myDataSet1.Customers;
MyDataSet.CustomersRow myDataRow =
myDataTable.NewCustomersRow();
myDataTable.AddCustomersRow(myDataRow);
sqlDataAdapter1.Update(myDataTable);
myDataRow = myDataTable.FindByCustomerID("J5COM");
Trang 290
Giáo trình lập trình cơ sở dữ liệu
myDataRow = myDataTable.FindByCustomerID("J5COM");
myDataTable.RemoveCustomersRow(myDataRow);
sqlDataAdapter1.Update(myDataTable);
sqlConnection1.Close();
}
Chương này trình bày cách cập nhật dữ liệu cho các dòng trong một DataSet và lưu
các thay đổi vào cơ sở dữ liệu thông qua đối tượng DataAdapter.
Bạn cũng đã biết cách tạo các ràng buộc cho đối tượng DataTable và DataColumn.
Việc này cho phép mô hình hóa các ràng buộc trong các bảng, các cột cơ sở dữ liệu vào
đối tượng DataTable hay DataColumn. Bằng cách tạo ràng buộc, ta có thể ngăn chặn
việc gán dữ liệu không mong muốn vào DataSet và do đó, hạn chế tối đa các lỗi xảy ra
trong quá cập nhật vào cơ sở dữ liệu.
Mỗi dòng trong DataTable được lưu bởi một đối tượng DataRow. Bạn có thể tìm
kiếm, trích lọc và sắp xếp các DataRow trong DataTable sử dụng các phương thức
Find(), Select() của DataTable.
Trang 291
Giáo trình lập trình cơ sở dữ liệu
Chương này cũng trình bày chi tiết các bước để thêm, cập nhật hay xóa các
DataRow từ DataTable, sau đó, lưu các thay đổi vào cơ sở dữ liệu. Để làm được điều
này, trước hết, bạn phải thiết lập các đối tượng Command cho DataAdapter. Mỗi
Command chứa một lệnh truy vấn SQL thích hợp như INSERT, UPDATE hay
DELETE. Các Command được lưu trong các thuộc tính InsertCommand,
UpdateCommand và DeleteCommand.
Các thay đổi trong DataSet được lưu vào cơ sở dữ liệu bởi phương thức Update()
của DataAdapter. Khi bạn thêm, thay đổi hay xóa các DataRow từ DataSet rồi gọi
phương thức Update() của DataAdapter, đối tượng Command thích hợp
(InsertCommand, UpdateCommand hay DeleteCommand) được thực thi để cập nhật các
thay đổi đó vào cơ sở dữ liệu.
Tính đồng thời (concurrency) xác định cách xử lý khi có nhiều người dùng cùng
thay đổi dữ liệu trên một dòng. Với optimistic concurrency, bạn chỉ có thể thay đổi một
dòng trong bảng của cơ sở dữ liệu khi không có ai khác thay đổi dòng đó kể từ khi nó
được tải vào DataSet. Đây là cách tốt nhất để dữ liệu của bạn không bị ghi đè bởi những
người dùng khác. Với “Last One Wins” concurrency, bạn có thể cập nhật một dòng và
lưu các thay đổi vào cơ sở dữ liệu bất cứ lúc nào. Dữ liệu của bạn có thể ghi đè lên
những gì đã được người dùng khác thay đổi trước đó. Vì thế, nên tránh sử dụng cách
này.
Bạn cũng có thể dùng đối tượng DataAdapter để gọi các thủ tục nhằm thêm, cập
nhật hay xóa các dòng khỏi cơ sở dữ liệu. Các Stored Procedure này được gọi thay cho
các lệnh INSERT, UPDATE và DELETE mà bạn dùng trong các thuộc tính
InsertCommand, UpdateCommand và DeleteCommand tương ứng.
Việc cung cấp các lệnh INSERT, UPDATE, DELETE hay các Stored Procedure để
cập nhật các thay đổi từ DataSet vào cơ sở dữ liệu đòi hỏi bạn phải viết khác nhiều mã
lệnh. Giải pháp để tránh tình trạng này là dùng đối tượng CommandBuilder. Đối tượng
này có thể tự động sinh ra các Command chứa lệnh truy vấn INSERT, UPDATE và
DELETE trên một bảng đơn. Sau đó, những Command này được dùng để gán cho các
thuộc tính InsertCommand, UpdateCommand và DeleteCommand của đối tượng
DataAdapter. Khi phương thức Update() của DataAdapter được gọi, đối tượng
Command được sinh tự động sẽ thực thi để lưu các thay đổi trong DataSet vào cơ sở dữ
liệu.
Chương này cũng đề cập đến việc xử lý các lỗi xảy ra trong quá trình cập nhật dữ
liệu, sử dụng các giao dịch với một DataSet cũng như cách cập nhật các dòng được lưu
trong một DataSet có định kiểu.
Trang 292
Giáo trình lập trình cơ sở dữ liệu
8. CHƯƠNG 8
TRÍCH LỌC VÀ SẮP XẾP VỚI DATAVIEW
Trong chương trước, bạn đã biết cách trích lọc và sắp xếp cá đối tượng DataRow
trong một DataTable bằng cách dùng phương thức Select(). Chương này cung cấp một
cách tiếp cận khác để trích lọc và sắp xếp các dòng trong một DataTable – đó là dùng
đối tượng DataView. Ưu điểm của DataView là có thể gắn (bind) nó cho một thành
phần giao diện của Windows Form (chẳng hạn như điều khiển DataGridView).
Một DataView lưu các bản sao của các dòng trong một DataTable. Mỗi bản sao của
các DataRow được lưu trong một đối tượng DataRowView. Các đối tượng
DataRowView cũng cho phép truy xuất các đối tượng DataRow nằm trong một
DataTable. Do đó, khi xem xét hay thay đổi nội dung của một DataRowView, thực sự,
ta đang làm việc với DataRow được lưu bên trong DataRowView đó.
Trang 293
Giáo trình lập trình cơ sở dữ liệu
Trang 294
Giáo trình lập trình cơ sở dữ liệu
CONSTANT DESCRIPTION
Added Dòng mới thêm.
CurrentRows Dòng hiện tại bao gồm luôn cả: Unchanged, Added, and
ModifiedCurrent.
Deleted Dòng đã bị xóa.
ModifiedCurrent Dòng hiện tại đã bị thay đổi.
ModifiedOriginal Dòng ban đầu trước tri thay đổi.
None Không phù hợp cho bất kỳ dòng nào trong DataTable.
OriginalRows Các dòng ban đầu bao gồm cả Unchanged và Deleted.
Unchanged Dòng chưa bị thay đổi.
Bảng 8.3. Enum DataViewRowState
Một trong những sự kiện của DataView là ListChanged – được xử lý bởi trình xử lý
sự kiện ListChangedEventHandler. Sự kiện này phát sinh khi danh sách được quản lý
bởi DataView thay đổi.
Bảng 8.3 liệt kê các giá trị trong enum System.Data.DataViewRowState. Enum này
được dùng trong thuộc tính RowState của DataTable. Thuộc tính này được dùng để chỉ
ra các dòng được lọc bởi DataView theo DataViewRowState.
Trang 295
Giáo trình lập trình cơ sở dữ liệu
Trong đó:
myDataTable: là DataTable mà DataView liên kết tới. DataView sẽ đọc các
dòng dữ liệu từ DataTable này. Giá trị của tham số này sẽ được gán cho thuộc
tính Table của DataView.
filterExpression: là một chuỗi chứa biểu thức điều kiện dùng để trích lọc các
dòng. Giá trị của tham số này được gán cho thuộc tính RowFilter.
sortExpression: là một chuỗi chứa biểu thức quy định các cột và cách sắp xếp
trên cột đó. Giá trị tham số này được gán cho thuộc tính Sort của DataView.
rowState: là một điều kiện bổ sung cho bộ lọc để lấy các DataRowView có trạng
thái tương ứng với rowState. Giá trị của tham số này được gán cho thuộc tính
RowStateFilter của DataView.
Ví dụ:
Trước khi tạo DataView, ta phải tạo một DataTable để chứa các dòng đọc được từ
cơ sở dữ liệu. Giả sử, ta đã có một DataTable với tên customersDT chứa dữ liệu lấy
được từ bảng Customers. Ta tạo các biến để chứa biểu thức điều kiện trích lọc, sắp xếp
và biến trạng thái RowState như sau:
string filterExpression = "Country = 'UK'";
string sortExpression = "CustomerID ASC, CompanyName DESC";
DataViewRowState rowStateFilter = DataViewRowState.OriginalRows;
Tiếp theo, sử dụng phương thức khởi tạo thứ 3 để tạo một DataView với tên
customersDV.
DataView customersDV =
new DataView(
customersDT, filterExpression, sortExpression, rowStateFilter
);
Một cách khác để tạo DataView là sử dụng phương thức khởi tạo thứ nhất. Sau đó,
gán giá trị cho các thuộc tính Table, RowFilter, Sort và RowStateFilter.
Một DataView lưu trữ các dòng trong các đối tượng DataRowView. Các
DataRowView đọc dữ liệu từ các đối tượng DataRow lưu trong DataTable bên dưới.
Đoạn mã sau sử dụng một vòng lặp for each để hiển thị các đối tượn DataRowView
trong DataView đã tạo ở trên. Trong đó, myDataRowView[count] trả về giá trị của cột
có chỉ số xác định bởi biến count.
foreach (DataRowView myDataRowView in customersDV)
{
for (int count = 0; count < customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(myDataRowView[count]);
Trang 296
Giáo trình lập trình cơ sở dữ liệu
}
Console.WriteLine("");
}
Chương trình ví dụ sau minh họa cách tạo một đối tượng DataView và dùng nó để
trích lọc, sắp xếp các hàng trong bảng Customers.
using System;
using System.Data;
using System.Data.SqlClient;
class UsingDataView
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();
Trang 297
Giáo trình lập trình cơ sở dữ liệu
BSBEV
B's Beverages
UK
CONSH
Consolidated Holdings
UK
EASTC
Eastern Connection
UK
ISLAT
Island Trading
UK
NORTS
North/South
UK
SEVES
Seven Seas Imports
UK
customersDT.PrimaryKey =
new DataColumn[]
{
customersDT.Columns["CustomerID"]
};
customersDV.ApplyDefaultSort = true;
Thuộc tính Sort của customersDV sau đó cũng được tự động gán bằng cột
CustomerID và làm cho các đối tượng DataRowView được sắp tăng theo giá trị trên cột
CustomerID.
Trang 299
Giáo trình lập trình cơ sở dữ liệu
Trang 300
Giáo trình lập trình cơ sở dữ liệu
Giả sử các đối tượng DataRowView trong DataView customersDV đã được sắp xếp
như sau:
AROUT, Around the Horn, UK
BSBEV, B's Beverages, UK
CONSH, Consolidated Holdings, UK
EASTC, Eastern Connection, UK
ISLAT, Island Trading, UK
NORTS, North/South, UK
SEVES, Seven Seas Imports, UK
Tiếp theo, ta gọi phương thức Find từ đối tượng customersDV để tìm chỉ số của
DataRowView có giá trị trên cột CustomerID là BSBEV. Vì BSBEV nằm ở dòng thứ 2
(có chỉ số là 1) nên giá trị trả về của phương thức Find() là 1.
int index = customersDV.Find("BSBEV"); // index == 1
8.5.3. Tìm các đối tượng DataRowView bằng phương thức FindRows
Phương thức FindRows() của đối tượng DataView tìm kiếm và trả về một mảng các
DataRowView có giá trị trên cột khóa chính trùng với một giá trị nào đó. Nếu không có
dòng nào được tìm thấy, phương thức này trả về một mảng không hức phần tử nào.
Nghĩa là thuộc tính Length của mảng có giá trị bằng 0.
Để tìm các DataRowView bằng phương thức FindRows(), trước hết, ta phải thiết lập
giá trị cho thuộc tính Sort của DataView bằng với tên cột khóa chính.
Ví dụ:
Nếu muốn tìm DataRowView dựa trên CustomerID, bạn phải gán giá trị cho thuộc
tính Sort của DataView là CustomerID, CustomerId ASC hoặc CustomerID DESC.
string sortExpression = "CustomerID";
customersDV.Sort = sortExpression;
Sau đó, gọi phương thức FindRows() để tìm DataRowView chứa giá trị BSBEV trên
cột CustomerID. Vì chỉ có một dòng thõa điều kiện nên phương thức này trả về mảng
chỉ chứa một DataRowView.
DataRowView[] customersDRVs = customersDV.FindRows("BSBEV");
Chương trình dưới đây minh họa chi tiết cách sử dụng các phương thức Find() và
FindRows() để tìm các DataRowView trong một DataView.
Chương trình 8.2: Tìm kiếm DataRowView dùng phương thức Find và FindRows
/*
FindingDataRowViews.cs illustrates the use of the Find() and
FindRows() methods of a DataView to find DataRowView objects
*/
using System;
using System.Data;
using System.Data.SqlClient;
Trang 301
Giáo trình lập trình cơ sở dữ liệu
class FindingDataRowViews
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();
Trang 302
Giáo trình lập trình cơ sở dữ liệu
Phương thức EndEdit() của DataRowView được gọi để kết thúc việc cập nhật dữ
liệu. Phương thức này tạo một DataRow mới bên dưới DataTable. Các đối tượng
DataColumn trong DataRow mới sẽ chứa các giá trị được gán trong đoạn mã trên.
Trang 303
Giáo trình lập trình cơ sở dữ liệu
Để lấy DataRow vừa được thêm vào DataTable, ta dùng thuộc tính Row của
DataRowView. Chẳng hạn:
Bạn có thể gọi phương thức RejectChanges() từ DataTable để hủy bỏ mọi thay đổi
bao gồm cả việc thêm DataRow, cập nhật hay xóa các DataRow khỏi DataTable.
Chương trình dưới đây minh họa cách thêm một DataRowView, cập nhật dữ liệu và
xóa các DataRowView từ DataView. Nó cũng hiển thị giá trị các thuộc tính IsNew,
IsEdit của các DataRowView để cho biết DataRowView mới hay đã bị thay đổi.
Trang 304
Giáo trình lập trình cơ sở dữ liệu
using System;
using System.Data;
using System.Data.SqlClient;
class AddModifyAndRemoveDataRowViews
{
public static void DisplayDataRow( DataRow myDataRow,
DataTable myDataTable )
{
Console.WriteLine("\nIn DisplayDataRow()");
foreach (DataColumn myDataColumn in myDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
myDataRow[myDataColumn]);
}
}
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();
Console.WriteLine("\nCalling customersDV.AddNew()");
DataRowView customerDRV = customersDV.AddNew();
customerDRV["CustomerID"] = "J7COM";
customerDRV["CompanyName"] = "J7 Company";
customerDRV["Country"] = "UK";
Console.WriteLine("customerDRV[\"CustomerID\"] = " +
Trang 305
Giáo trình lập trình cơ sở dữ liệu
customerDRV["CustomerID"]);
Console.WriteLine("customerDRV[\"CompanyName\"] = " +
customerDRV["CompanyName"]);
Console.WriteLine("customerDRV[\"Country\"] = " +
customerDRV["Country"]);
customerDRV.EndEdit();
customersDV[0].BeginEdit();
customersDV[0]["CompanyName"] = "Widgets Inc.";
Console.WriteLine("customersDV[0][\"CustomerID\"] = " +
customersDV[0]["CustomerID"]);
Console.WriteLine("customersDV[0][\"CompanyName\"] = " +
customersDV[0]["CompanyName"]);
Console.WriteLine("customersDV[0].IsNew = " +
customersDV[0].IsNew);
Console.WriteLine("customersDV[0].IsEdit = " +
customersDV[0].IsEdit);
customersDV[0].EndEdit();
DisplayDataRow(customersDV[0].Row, customersDT);
customersDV.Delete(1);
Console.WriteLine("customersDV[1].IsNew = " +
customersDV[1].IsNew);
Console.WriteLine("customersDV[1].IsEdit = " +
customersDV[1].IsEdit);
Console.WriteLine("\ncustomersDV[2][\"CustomerID\"] = " +
customersDV[2]["CustomerID"]);
Console.WriteLine("\nCalling customersDV[2].Delete()");
customersDV[2].Delete();
Trang 306
Giáo trình lập trình cơ sở dữ liệu
customersDT.AcceptChanges();
In DisplayDataRow()
CustomerID = J7COM
CompanyName = J7 Company
Country = UK
In DisplayDataRow()
CustomerID = AROUT
CompanyName = Widgets Inc.
Country = UK
customersDV[1]["CustomerID"] = BSBEV
Calling customersDV.Delete(1)
customersDV[1].IsNew = False
customersDV[1].IsEdit = False
customersDV[2]["CustomerID"] = EASTC
Calling customersDV[2].Delete()
Trang 307
Giáo trình lập trình cơ sở dữ liệu
NORTS, North/South, UK
SEVES, Seven Seas Imports, UK
J7COM, J7 Company, UK
Giả sử, ta có một DataView có tên customersDV dùng để xem các khách hàng có giá
trị trong cột Country là UK. Sau đó, bạn gọi phương thức CreateChildView() từ một
DataRowView trong customersDV để tạo một DataView con. Khi gọi phương thức này,
ta phải đưa tên của quan hệ hoặc một đối tượng DataRelation vào tham số của hàm:
DataView ordersDV = customersDV[0].CreateChildView("CustomersOrders");
DataView có tên ordersDV cho phép bạn truy xuất tới các dòng con trong DataTable
có tên ordersDT. Dòng cha trong ví dụ này là DataRowView đầu tiên trong
customerDV với giá trị trên cột CustomerID là AROUT. DataView con có tên
ordersDV chứa các DataRowView là chi tiết các đơn hàng của khách hàng AROUT.
Chương trình sau minh họa chi tiết cách tạo một DataView con.
Chương trình 8.4: Tạo DataView con bởi phương thức CreateChildView
/*
CreateChildDataView.cs illustrates how to create a
child DataView
*/
using System;
using System.Data;
using System.Data.SqlClient;
class CreateChildDataView
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
Trang 308
Giáo trình lập trình cơ sở dữ liệu
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Country " +
"FROM Customers;" +
"SELECT OrderID, CustomerID " +
"FROM Orders;";
mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Customers";
myDataSet.Tables["Table1"].TableName = "Orders";
DataRelation customersOrdersDataRel =
new DataRelation( "CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
myDataSet.Relations.Add( customersOrdersDataRel );
Console.WriteLine("Customer:");
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(customersDV[0][count]);
}
DataView ordersDV =
customersDV[0].CreateChildView("CustomersOrders");
Trang 309
Giáo trình lập trình cơ sở dữ liệu
Trang 310
Giáo trình lập trình cơ sở dữ liệu
Sau đó, gán giá trị cho các thuộc tính Sort và RowFilter. Các giá trị này sẽ được sử
dụng bởi DataView chứa DataTable Customers.
myDVM.DataViewSettings["Customers"].Sort = "CustomerID";
myDVM.DataViewSettings["Customers"].RowFilter = "Country = 'UK'";
Đoạn mã trên thực sự chưa tạo DataView, nó chỉ gán giá trị cho các thuộc tính của
DataView sẽ được tạo để xem các dòng trong DataTable Customers.
Tiếp theo, gọi phương thức CreateDataView() của đối tượng DataViewManager để
tạo một DataView thực sự. Truyền DataTable customersDT vào tham số của hàm:
DataView customersDV = myDVM.CreateDataView(customersDT);
Giá trị của các thuộc tính Sort và RowFilter của DataView customersDV sẽ được
gán lần lượt là CustomerID và Country = ‘UK’. Các giá trị này được lấy từ các thiết lập
trong thuộc tính DataViewSettings ở trên.
using System;
using System.Data;
using System.Data.SqlClient;
class UsingDataViewManager
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
Trang 311
Giáo trình lập trình cơ sở dữ liệu
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();
myDVM.DataViewSettings["Customers"].Sort = "CustomerID";
myDVM.DataViewSettings["Customers"].RowFilter =
"Country = 'UK'";
Console.WriteLine("myDVM.DataViewSettingCollectionString = " +
myDVM.DataViewSettingCollectionString + "\n");
Trang 312
Giáo trình lập trình cơ sở dữ liệu
RowStateFilter="CurrentRows"/>
</DataViewSettingCollectionString>
Trang 313
Giáo trình lập trình cơ sở dữ liệu
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
Trong chương này, bạn đã học cách sử dụng đối tượng DataView để trích lọc và sắp
xếp các dòng. Ưu điểm của DataView là có thể gắn nó cho một thành phần điều khiển
trên Windows Form (chẳng hạn như DataGridView).
Một DataView lưu trữ bản sao của các dòng trong một DataTable bởi các đối tượng
DataRowView. Các đối tượng DataRowView cho phép truy xuất tới các DataRow trong
DataTable bên dưới. Do đó, khi lấy hay thay đổi nội dung trong DataRowView, thực sự,
bạn đang làm việc với DataRow bên dưới.
Thuộc tính RowFilter của DataView tương tự như mệnh đề WHERE của lệnh
SELECT. Do đó, bạn có thể dùng tính năng này để tạo các biểu thức chứa điều kiện
trích lọc cho DataView. Bạn có thể sử dụng các toán tử AND, OR, NOT, IN, LIKE,
toán tử so sánh, toán tử số học, các ký tự đại diện (*, %) và cả các hàm tính toán.
Phương thức Find() của đối tượng DataView cho phép lấy chỉ số của một
DataRowView trong DataView. Bạn cũng có thể lấy một mảng các DataRowView bằng
cách dùng phương thức FindRows() của DataView.
Một đối tượng DataViewManager cho phép bạn quản lý tập trung nhiều đối tượng
DataView trong một DataSet. Ngoài ra, nó cũng cho phép bạn tạo các đối tượng
DataView một cách dễ dàng trong lúc chạy chương trình.
Trang 315
Giáo trình lập trình cơ sở dữ liệu
9. CHƯƠNG 9
QUAN HỆ VÀ RÀNG BUỘC
Trong chương 1, ta đã biết, các bảng trong cơ sở dữ liệu có quan hệ với nhau thông
qua khóa chính và khóa ngoại. Bảng chứa khóa chính được gọi là bảng cha, bảng chứa
khóa ngoại còn được gọi là bảng con. Quan hệ khóa ngoại dùng để định nghĩa mối quan
hệ cha con giữa hai bảng.
Tương tự, ta cũng có thể tạo quan hệ cha con giữa hai đối tượng DataTable bằng
cách dùng đối tượng ForeignKeyConstraint hoặc DataRelation. Mặc định, khi thêm một
đối tượng DataRelation vào DataSet, thì thực sự, một đối tượng UniqueConstraint được
thêm vào DataTable cha và một đối tượng ForeignKeyConstraint được thêm vào
DataTable con. ForeignKeyConstraint bảo đảm rằng mỗi DataRow trong DataTable con
phải khớp với (hay tồn tại tương ứng) một DataRow trong DataTable cha.
Chương này trình bày chi tiết cách sử dụng các đối tượng UniqueConstraint,
ForeignKeyConstraint và DataRelation. Bạn cũng sẽ được học cách điều hướng các
dòng trong các DataTable có quan hệ với nhau, thay đổi dữ liệu trên các DataTable đó
và cuối cùng là cập nhật các thay đổi lên cơ sở dữ liệu. Để cải thiện hiệu suất khi tải một
DataTable có chứa một lượng lớn đối tượng DataRow, cần phải gán thuộc tính
EnforceConstraints của DataSet là false nhằm tiết kiệm thời gian tải. Sau đó, gán nó trở
lại giá trị mặc định là true sau khi tải xong dữ liệu.
Trang 316
Giáo trình lập trình cơ sở dữ liệu
Trước khi tạo và thêm một đối tượng UniqueConstraint, cần phải tạo một đối tượng
DataTable. Đoạn mã sau minh họa cách tạo và xử lý hai đối tượng DataTable có tên
customersDT và ordersDT.
Trang 317
Giáo trình lập trình cơ sở dữ liệu
myDataSet.Tables["Table"].TableName = "Customers";
myDataSet.Tables["Table1"].TableName = "Orders";
Tiếp theo, ta tạo một đối tượng UniqueConstraint áp dụng lên đối tượng
DataColumn có tên CustomerID của DataTable customersDT. Sau đó, thêm đối tượng
UniqueConstraint này vào customersDT. Lưu ý rằng, tham số thứ 3 trong phương thức
tạo lập của UniqueConstraint được gán là true, chỉ ra ràng đây là rằng buộc khóa chính.
UniqueConstraint myUC =
new UniqueConstraint(
"UniqueConstraintCustomerID",
customersDT.Columns["CustomerID"],
true
);
customersDT.Constraints.Add(myUC);
Việc thêm một đối tượng UniqueConstraint vào DataTable trên một DataColumn
nào đó chỉ thành công khi mọi DataRow trong DataTable có giá trị phân biệt trên
DataColumn đó.
Đoạn mã sau minh họa cách lấy một ràng buộc từ DataTable và hiển thị giá trị các
thuộc tính được thiết lập trong ràng buộc đó.
myUC = (UniqueConstraint)
customersDT.Constraints["UniqueConstraintCustomerID"];
Console.WriteLine("Columns:");
Columns:
CustomerID
myUC.ConstraintName = UniqueConstraintCustomerID
myUC.IsPrimaryKey = True
myUC.Table = Customers
Trang 318
Giáo trình lập trình cơ sở dữ liệu
Lưu ý: thuộc tính IsPrimaryKey có giá trị là true vì đây là ràng buộc khóa chính. Giá
trị này được thiết lập qua phương thức tạo lập khi tạo đối tượng UniqueConstraint.
Trang 319
Giáo trình lập trình cơ sở dữ liệu
ForeignKeyConstraint(DataColumn[] parentDataColumns,
DataColumn[] childDataColumns)
ForeignKeyConstraint(string constraintName,
DataColumn parentDataColumn,
DataColumn childDataColumn)
ForeignKeyConstraint(string constraintName,
DataColumn[] parentDataColumns,
DataColumn[] childDataColumns)
ForeignKeyConstraint(string constraintName,
string parentDataTableName,
string[] parentDataColumnNames,
string[] childDataColumnNames,
AcceptRejectRule acceptRejectRule,
Rule deleteRule, Rule updateRule)
Trong đó:
parentDataColumn và parentDataColumns: là các đối tượng DataColumn trong
DataTable cha được áp dụng ràng buộc.
childDataColumn và childDataColumns: là các đối tượng DataColumnh trong
DataTable con được áp dụng ràng buộc.
constraintName: tên của ràng buộc được gán cho thuộc tính ConstraintName của
đối tượng ForeignKeyConstraint.
parentDataTableName: là tên của DataTable cha.
parentDataColumnNames và childDataColumnNames: chứa tên của các đối
tượng DataColumn trong DataTable cha và DataTable con.
acceptRejectRule, deleteRule và updateRule: là các quy tắc xử lý cho ràng buộc
ForeignKeyConstraint.
Trang 320
Giáo trình lập trình cơ sở dữ liệu
Đoạn mã sau minh họa cách tạo một đối tượng ForeignKeyConstraint biểu diễn ràng
buộc được áp dụng lên DataColumn có tên CustomerID của DataTable ordersDT tới
DataColumn CustomerID của DataTable customersDT.
ForeignKeyConstraint myFKC =
new ForeignKeyConstraint(
"ForeignKeyConstraintCustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
ordersDT.Constraints.Add(myFKC);
Chú ý rằng, ràng buộc ForeignKeyConstraint được thêm vào DataTable ordersDT
bằng phương thức Add. Việc thêm ràng buộc này thành công chỉ khi mỗi giá trị trên
DataColumn trong DataTable con phải có một giá trị tương ứng trên DataColumn của
DataTable cha.
Đoạn mã tiếp theo minh họa cách lấy lại ràng buộc được áp dụng cho DataTable
ordersDT và hiển thị các thuộc tính của nó.
myFKC = (ForeignKeyConstraint)
ordersDT.Constraints["ForeignKeyConstraintCustomersOrders"];
Console.WriteLine("myFKC.AcceptRejectRule = " +
myFKC.AcceptRejectRule);
Console.WriteLine("Columns:");
foreach (DataColumn myDataColumn in myFKC.Columns)
{
Console.WriteLine("" + myDataColumn);
}
Trang 321
Giáo trình lập trình cơ sở dữ liệu
Trang 322
Giáo trình lập trình cơ sở dữ liệu
Một khi đã tạo được quan hệ bởi đối tượng DataRelation, ta có thể dùng phương
thức GetChildRows() của các đối tượng DataRow trong DataTable cha để lấy các đối
tượng DataRow “tương ứng” từ DataTable con. Từ “tương ứng” có nghĩa là các dòng
trong DataTable con có giá trị trên cột khóa ngoại khớp với giá trị trên cột khóa chính
của DataTable cha. Ta cũng có thể dùng phương thức GetParentRow() của các đối
tượng DataRow trong DataTable con để lấy ra DataRow tương ứng trong DataTable
cha.
Trước khi tạo và thêm một đối tượng DataRelation, ta cần phải tạo một DataSet.
Đoạn mã sau minh họa cách tạo và xử lý một DataSet chứa hai đối tượng DataTable có
tên customersDT và ordersDT. Lưu ý: hai dòng đầu của bảng Customers đi kèm tương
ứng với các dòng trong bảng Orders nhận được như sau:
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT TOP 2 CustomerID, CompanyName " +
"FROM Customers " +
"ORDER BY CustomerID;" +
"SELECT OrderID, CustomerID " +
"FROM Orders " +
"WHERE CustomerID IN (" +
" SELECT TOP 2 CustomerID " +
" FROM Customers " +
" ORDER BY CustomerID" +
")";
mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();
myDataSet.Tables["Table"].TableName = "Customers";
myDataSet.Tables["Table1"].TableName = "Orders";
DataRelation(string dataRelationName,
DataColumn[] parentDataColumns,
DataColumn[] childDataColumns)
Trang 323
Giáo trình lập trình cơ sở dữ liệu
DataRelation(string dataRelationName,
DataColumn parentDataColumn,
DataColumn childDataColumn,
bool createConstraints)
DataRelation(string dataRelationName,
DataColumn[] parentDataColumns,
DataColumn[] childDataColumns,
bool createConstraints)
DataRelation(string dataRelationName,
string parentDataTableName,
string childDataTableName,
string[] parentDataColumnNames,
string[] childDataColumnNames,
bool nested)
Trong đó:
dataRelationName: là tên muốn gán cho thuộc tính RelationName của đối tượng
DataRelation.
parentDataColumn và parentDataColumns: là các đối tượng DataColumn trong
DataTable cha.
childDataColumn và childDataColumns: là các đối tượng DataColumn trong
DataTable con.
createConstraints: chỉ cho biết bạn muốn tự động thêm một ràng buộc
UniqueConstraint vào DataTable cha và một ràng buộc ForeignKeyConstraint
vào DataTable con. Giá trị mặc định là true.
parentDataTableName và childDataTableName: là tên của các đối tượng
DataTable cha và con.
parentDataColumnNames và childDataColumnNames: chứa tên của các đối
tượng DataColumn trong DataTable cha và DataTable con.
Nested: cho biết mối quan hệ lồng nhau hay không.
Đoạn mã sau minh họa cách tạo một đối tượng DataRelation tên là customersOrders-
DataRel:
DataRelation customersOrdersDataRel =
new DataRelation(
"CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
Tên được gán cho thuộc tính RelationName của đối tượng customersOrdersDataRel
là CustomersOrders. DataColumn cha là customersDT.Columns[“CustomerID”] và
DataColumn con là ordersDT.Columns[“CustomerID”].
Tiếp theo, customersOrdersDataRel phải được thêm vào DataSet. Việc truy xuất đến
các DataRelation trong đối tượng DataSet được thực hiện qua thuộc tính Relationships.
Thuộc tính Relationships trả về một đối tượng DataRelationCollection, chứa một tập
các quan hệ tồn tại trong DataSet. Để thêm một đối tượng DataRelation vào đối tượng
Trang 324
Giáo trình lập trình cơ sở dữ liệu
DataRelationCollection của DataSet, ta dùng phức Add() qua thuộc tính Relationships
của DataSet.
myDataSet.Relations.Add(
customersOrdersDataRel
);
Phương thức Add còn có một dạng khác để tạo và thêm DataRelation vào DataSet.
myDataSet.Relations.Add(
"CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
Đoạn mã này cùng làm một việc là thêm một DataRelation vào DataSet. Tham số
thứ nhất của phương thức Add là một chuỗi chứa tên mà bạn muốn gán cho thuộc tính
RelationName của DataRelation. Tham số thứ 2 và thứ 3 là các đối tượng DataColumn
trong DataTable cha và DataTable con.
Đoạn mã sau minh họa cách liệt kê các thuộc tính của đối tượng UniqueConstraint
cùng giá trị của chúng.
Console.WriteLine("Columns:");
foreach (DataColumn myDataColumn in myUC.Columns)
{
Console.WriteLine("" + myDataColumn);
}
Kết quả
Columns:
CustomerID
myUC.ConstraintName = Constraint1
myUC.IsPrimaryKey = False
myUC.Table = Customers
Trang 325
Giáo trình lập trình cơ sở dữ liệu
Ngoài ra, bạn có thể lấy đối tượng ForeignKeyConstraint từ một DataRelation bằng
cách dùng thuộc tính ChildKeyConstraint của DataRelation. Chẳng hạn:
ForeignKeyConstraint myFKC =
customersOrdersDataRel.ChildKeyConstraint;
Đoạn mã sau minh họa cách lấy thông tin được lưu trong ràng buộc
ForeignKeyConstraint và hiển thị chúng lên màn hình.
Console.WriteLine("myFKC.AcceptRejectRule = " +
myFKC.AcceptRejectRule);
Console.WriteLine("Columns:");
Kết quả
myFKC.AcceptRejectRule = None
Columns:
CustomerID
myFKC.ConstraintName = CustomersOrders
myFKC.DeleteRule = Cascade
RelatedColumns:
CustomerID
myFKC.RelatedTable = Customers
myFKC.Table = Orders
myFKC.UpdateRule = Cascade
Các thuộc tính DeleteRule và UpdateRule được đặt là Cascade theo mặc định. Vì
DeleteRule được gán là Cascade nên khi xóa một DataRow trong DataTable cha thì tất
cả các DataRow tương ứng trong DataTable con cũng bị xóa theo. Tương tự, vì
UpdateRule được gán là Cascade nên khi DataColumn trong DataTable cha có liên quan
đến ràng buộc khóa ngoại ForeignKeyConstraint bị thay đổi thì các thay đổi đó cũng
được áp dụng cho các đối tượng DataRow tương ứng trong DataTable con.
9.7. Quản lý đối tượng DataRow trong các DataTable cha và con
Trang 326
Giáo trình lập trình cơ sở dữ liệu
Để điều hướng các đối tượng DataRow trong các DataTable có quan hệ với nhau, ta
dùng các phương thức GetChildRow() và GetParentRow() của đối tượng DataRow.
DataRow[] ordersDRs =
customerDR.GetChildRows("CustomersOrders");
Console.WriteLine("This customer placed the following orders:");
Kết quả
CustomerID = ALFKI
CompanyName = Alfreds Futterkiste
This customer placed the following orders:
OrderID = 10643
OrderID = 10692
OrderID = 10702
OrderID = 10835
OrderID = 10952
OrderID = 11011
CustomerID = ANATR
CompanyName = Ana Trujillo Emparedados y helados
This customer placed the following orders:
OrderID = 10308
OrderID = 10625
OrderID = 10759
OrderID = 10926
Trang 327
Giáo trình lập trình cơ sở dữ liệu
Kết quả
9.8. Thêm, cập nhật và xóa các dòng có quan hệ với nhau
Trong phần này, ta sẽ tạo ra các thay đổi trong các đối tượng DataTable, sau đó, cập
nhật các thay đổi vào các bảng của cơ sở dữ liệu theo một thứ tự nhất định. Nếu không,
chương trình sẽ phát sinh ra một ngoại lệ (lỗi).
Để minh họa cách cập nhật dữ liệu, ta sẽ dùng hai DataTable để chứa các dòng lấy
từ hai bảng Customers và Orders của cơ sở dữ liệu Northwind. Hai bảng này có quan hệ
với nhau thông qua khóa ngoại CustomerID.
Để thực hiện được điều này, cần phải có hai đối tượng DataAdapter:
customersDA: để làm việc với bảng Customers
ordersDA: để làm việc với bảng Orders
Đầu tiên, đoạn mã sau tạo một DataAdapter tên là customersDA và thiết lập các
thuộc tính để chứa các lệnh SELECT, INSERT, UPDATE, DELETE cần thiết để truy
cập đến bảng Customers.
Trang 328
Giáo trình lập trình cơ sở dữ liệu
customersInsertCommand.Parameters.Add("@CustomerID",
SqlDbType.NChar, 5, "CustomerID");
customersInsertCommand.Parameters.Add("@CompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
customersUpdateCommand.Parameters.Add("@NewCompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
customersUpdateCommand.Parameters.Add("@OldCustomerID",
SqlDbType.NChar, 5, "CustomerID");
customersUpdateCommand.Parameters.Add("@OldCompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
customersUpdateCommand.Parameters["@OldCustomerID"].SourceVersion =
DataRowVersion.Original;
customersUpdateCommand.Parameters["@OldCompanyName"].SourceVersion =
DataRowVersion.Original;
customersDeleteCommand.Parameters.Add("@OldCustomerID",
SqlDbType.NChar, 5, "CustomerID");
customersDeleteCommand.Parameters.Add("@OldCompanyName",
SqlDbType.NVarChar, 40, "CompanyName");
customersDeleteCommand.Parameters["@OldCustomerID"].SourceVersion =
DataRowVersion.Original;
customersDeleteCommand.Parameters["@OldCompanyName"].SourceVersion =
DataRowVersion.Original;
Trang 329
Giáo trình lập trình cơ sở dữ liệu
Lưu ý rằng, lệnh UPDATE chỉ cập nhật giá trị trên cột CompanyName, không thay
đổi giá trị của cột khóa chính CustomerID.
Thứ hai, ta tạo một đối tượng DataAdapter khác, đặt tên là ordersDA và thiết lập các
thuộc tính của nó để chứa các lệnh SELECT, INSERT, UPDATE, DELETE cần thiết để
truy xuất bảng Orders trong cơ sở dữ liệu Northwind. Chú ý rằng, đối tượng
ordersInsertCommand chứa cả lệnh INSERT và lệnh SELECT để lấy về giá trị mới
được tạo tự động trên cột OrderID.
ordersInsertCommand.Parameters.Add("@CustomerID",
SqlDbType.NChar, 5, "CustomerID");
ordersInsertCommand.Parameters.Add("@ShipCountry",
SqlDbType.NVarChar, 15, "ShipCountry");
ordersInsertCommand.Parameters.Add("@OrderID",
SqlDbType.Int, 0, "OrderID");
ordersInsertCommand.Parameters["@OrderID"].Direction =
ParameterDirection.Output;
Trang 330
Giáo trình lập trình cơ sở dữ liệu
ordersUpdateCommand.Parameters.Add("@NewShipCountry",
SqlDbType.NVarChar, 15, "ShipCountry");
ordersUpdateCommand.Parameters.Add("@OldOrderID",
SqlDbType.Int, 0, "OrderID");
ordersUpdateCommand.Parameters.Add("@OldCustomerID",
SqlDbType.NChar, 5, "CustomerID");
ordersUpdateCommand.Parameters.Add("@OldShipCountry",
SqlDbType.NVarChar, 15, "ShipCountry");
ordersUpdateCommand.Parameters["@OldOrderID"].
SourceVersion = DataRowVersion.Original;
ordersUpdateCommand.Parameters["@OldCustomerID"].
SourceVersion = DataRowVersion.Original;
ordersUpdateCommand.Parameters["@OldShipCountry"].
SourceVersion = DataRowVersion.Original;
ordersDeleteCommand.Parameters.Add("@OldOrderID",
SqlDbType.Int, 0, "OrderID");
ordersDeleteCommand.Parameters.Add("@OldCustomerID",
SqlDbType.NChar, 5, "CustomerID");
ordersDeleteCommand.Parameters.Add("@OldShipCountry",
SqlDbType.NVarChar, 15, "ShipCountry");
ordersDeleteCommand.Parameters["@OldOrderID"].
SourceVersion = DataRowVersion.Original;
ordersDeleteCommand.Parameters["@OldCustomerID"].
SourceVersion = DataRowVersion.Original;
ordersDeleteCommand.Parameters["@OldShipCountry"].
SourceVersion = DataRowVersion.Original;
Trang 331
Giáo trình lập trình cơ sở dữ liệu
Tiếp theo, tạo một DataSet có tên myDataSet để lưu trữ và xử lý dữ liệu lấy từ hai
bảng Customers và Orders thông qua hai DataAdapter customersDA và ordersDA.
Trong đoạn mã dưới đây, hai DataTable tạo được có tên tương ứng là customersDT và
ordersDT.
customersDA.Fill(myDataSet, "Customers");
ordersDA.Fill(myDataSet, "Orders");
mySqlConnection.Close();
Để thiết lập thuộc tính PrimaryKey cho hai DataTable, ta làm như sau:
customersDT.PrimaryKey =
new DataColumn[]
{
customersDT.Columns["CustomerID"]
};
ordersDT.PrimaryKey =
new DataColumn[]
{
ordersDT.Columns["OrderID"]
};
Sau đó, thiết lập giá trị cho các thuộc tính của DataColumn OrderID trong
DataTable ordersDT để nó là cột định danh (identity)
ordersDT.Columns["OrderID"].AllowDBNull = false;
ordersDT.Columns["OrderID"].AutoIncrement = true;
ordersDT.Columns["OrderID"].AutoIncrementSeed = -1;
ordersDT.Columns["OrderID"].AutoIncrementStep = -1;
ordersDT.Columns["OrderID"].ReadOnly = true;
ordersDT.Columns["OrderID"].Unique = true;
Cuối cùng, thêm một đối tượng DataRelation vào DataSet để tạo ra một quan hệ
giữa hai DataTable customersDT và ordersDT thông qua DataColumn CustomerID.
DataRelation customersOrdersDataRel =
new DataRelation(
"CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
myDataSet.Relations.Add(
customersOrdersDataRel
);
Trang 332
Giáo trình lập trình cơ sở dữ liệu
Tiếp theo, thêm một DataRow có tên orderDR vào DataTable ordersDT. Chú ý rằng,
giá trị trên cột CustomerID cũng được gán là J6COM. Điều này cho biết, orderDR là
DataRow con của DataRow customerDR đã thêm vào DataTable customersDT ở bước
trước.
Vì DataColumn OrderID của DataTable ordersDT được thiết lập là cột định danh
nên giá trị của nó sẽ được gán tự động. Khi DataRow này được cập nhật vào cơ sở dữ
liệu, lệnh SELECT trong DataAdapter ordersDA sẽ gán giá trị mới được sinh bởi cơ sở
dữ liệu cho cột OrderID.
customerDR.Delete();
Trong phần khảo sát các ràng buộc tạo bởi DataRelation, ta đã biết: mặc định, một
ràng buộc ForeignKeyConstraint sẽ được thêm vào DataTable con khi đối tượng
DataRelation được thêm vào DataSet. Giá trị thuộc tính DeleteRule của đối tượng
ForeignKeyConstraint này cũng được gán giá trị mặc định là Cascade, có nghĩa là khi
một DataRow trong DataTable cha bị xóa thì các DataRow tương ứng trong DataTable
con cũng bị xóa theo.
Vì thế, trong trường hợp này, việc xóa đối tượng DataRow customerDR cũng làm
cho đối tượng DataRow orderDR trong DataTable ordersDT bị xóa theo.
Trang 333
Giáo trình lập trình cơ sở dữ liệu
Để lấy các đối tượng DataRow vừa được thêm vào, cập nhật hay bị xóa, ta dùng
phương thức Select của DataTable. Phương thức Select có nhiều dạng, ở đây, ta có thể
sử dụng dạng quá tải hàm sau:
Trong đó:
filterExpression: chỉ ra điều kiện lọc để chọn các dòng.
sortExpression: chỉ ra thứ tự sắp xếp các dòng
myDataViewRowState: chỉ ra trạng thái của các dòng muốn chọn.
Để lấy các đối tượng DataRow được thêm vào DataTable customersDT, ta dùng
phương thức Select của DataTable như sau:
DataRow[] newCustomersDRArray =
customersDT.Select("", "", DataViewRowState.Added);
Chú ý rằng, hằng số Added được lấy từ enum DataViewRowState cho biết chỉ các
đối tượng DataRow mới được thêm vào customersDT mới được trả về và lưu vào một
mảng có tên newCustomersDRArray.
Tiếp theo, ta có thể cập nhật các DataRow này vào bảng Customers trong cơ sở dữ
liệu bảng cách gọi phương thức Update() của đối tượng DataAdapter customersDA. Giá
Trang 334
Giáo trình lập trình cơ sở dữ liệu
trị numOfRows cho biết số dòng thực sự đã được thêm vào bảng Customers trong cơ sở
dữ liệu.
Đoạn mã sau minh họa sáu bước đã chỉ ra ở trên để cập nhật tất cả các thay đổi vào
cơ sở dữ liệu. Chú ý rằng, tại mỗi bước, ta phải dùng các hằng số DataViewRowState
khác nhau để lấy các đối tượng DataRow theo yêu cầu.
Chương trình 9.3: Cập nhật các thay đổi vào cơ sở dữ liệu
DataRow[] newCustomersDRArray =
customersDT.Select("", "", DataViewRowState.Added);
DataRow[] newOrdersDRArray =
ordersDT.Select("", "", DataViewRowState.Added);
numOfRows = ordersDA.Update(newOrdersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);
DataRow[] modifiedCustomersDRArray =
customersDT.Select("", "", DataViewRowState.ModifiedCurrent);
numOfRows = customersDA.Update(modifiedCustomersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);
DataRow[] modifiedOrdersDRArray =
ordersDT.Select("", "", DataViewRowState.ModifiedCurrent);
numOfRows = ordersDA.Update(modifiedOrdersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);
Trang 335
Giáo trình lập trình cơ sở dữ liệu
DataRow[] deletedOrdersDRArray =
ordersDT.Select("", "", DataViewRowState.Deleted);
numOfRows = ordersDA.Update(deletedOrdersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);
DataRow[] deletedCustomersDRArray =
customersDT.Select("", "", DataViewRowState.Deleted);
numOfRows = customersDA.Update(deletedCustomersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);
mySqlConnection.Close();
}
Lưu ý: Phương thức này được gọi ngay sau khi thực hiện xong mỗi bước thêm, cập nhật
và xóa các DataRow.
9.9. Các vấn đề liên quan đến việc cập nhật khóa chính
Trong phần này, ta sẽ tìm hiểu các vấn đề liên quan xảy ra khi cố gắng thay đổi giá
trị trên cột khóa chính của một DataTable cha, sau đó cập nhật giá trị này vào cơ sở dữ
liệu. Những vấn đề này xảy ra khi bảng con trong cơ sở dữ liệu đã tồn tại các dòng có
cùng giá trị trên cột khóa chính mà bạn muốn thay đổi trên bảng cha.
Để tránh gặp rắc rỗi và lỗi phát sinh khi cập nhật các thay đổi vào cơ sở dữ liệu, cách
tốt nhất là không cho phép thay đổi giá trị trên cột khóa chính của bảng. Việc này có thể
dễ dàng thực hiện bằng cách thiết lập giá trị cho thuộc tính ReadOnly là true cho các
DataColumn thuộc nhóm khóa chính của DataTable cha. Đồng thời, đặt giá trị cho
thuộc tính ReadOnly là true cho các DataColumn thuộc nhóm khóa ngoại của
DataTable con. Điều này ngăn chặn được sự thay đổi giá trị trên các DataColumn đó.
Nếu bạn thực sự muốn và cần thiết phải thay đổi giá trị trên cột khóa chính và khóa
ngoại, cách tốt nhất là xóa chúng sau đó tạo các dòng mới trong cơ sở dữ liệu với các
giá trị khóa chính và khóa ngoại mới.
Việc quản lý các thay đổi xảy ra trên khóa chính và khóa ngoại có thể được thực
hiện bằng cách dùng các thuộc tính của quan hệ khóa ngoại trong cơ sở dữ liệu SQL
Server hoặc dùng các thuộc tính UpdateRule, DeleteRule của đối tượng ForeignKey-
Constraint.
9.9.1. Điều khiển việc cập nhật và xóa dùng SQL Server
Bằng cách thiết lập giá trị cho các thuộc tính của quan hệ khóa ngoại trong SQL
Server, ta có thể điều khiển cách xử lý của cơ sở dữ liệu khi cập nhật hay xóa dữ liệu
liên quan đến các cột khóa chính và khóa ngoại.
Trang 336
Giáo trình lập trình cơ sở dữ liệu
Bạn có thể thiết lập các thuộc tính này bằng cách dùng thẻ Relationship trong hộp
thoại Properties của một bảng trong cơ sở dữ liệu.
Chẳng hạn, trong trình Enterprise Manager, để mở hộp thoại Properties của bảng
Orders ta thực hiện các bước sau:
- Trong trình Enterprise Manager, chon mục Tables của cơ sở dữ liệu Northwind,
nhắp phải chuột lên bảng Orders
- Chọn Design Table trong menu ngữ cảnh
- Nhấn nút Manage Relationships trên thanh công cụ của cửa sổ Design Table
- Chọn khóa ngoại mà bạn muốn khảo sát trong mục Select Relationship
Hình 9.1 cho thấy thẻ Relationships chứa thông tin chi tiết về khóa ngoại có tên
FK_Orders_Customers biểu diễn một quan hệ khóa ngoại trên cột CustomerID giữa hai
bảng Orders và Customers.
Hộp kiểm Cascade Update Related Fields chỉ ra rằng, việc thay đổi giá trị trên cột
khóa chính của bảng chứa khóa chính (bảng cha) cũng làm thay đổi giá trị của cột khóa
ngoại trên các dòng tương ứng trong bảng chứa khóa ngoại (bảng con).
Chẳng hạn, giả sử hộp kiểm này được chọn và giá trị trên cột CustomerID trong một
dòng của bảng Customers bị thay đổi từ ALFKI thành ANATR thì khi đó, giá trị
ALFKI trên cột CustomerID của các dòng trong bảng Orders cũng bị thay đổi thành
ANATR.
Tương tự, hộp kiểm Cascade Delete Related Records chỉ ra rằng, việc xóa một dòng
trong bảng chứa khóa chính cũng sẽ xóa các dòng có liên quan trong bảng chứa khóa
ngoại. Chẳng hạn, nếu hộp kiểm này được chọn và một dòng trong bảng Customers với
cột CustomerID có giá trị ANTON bị xóa thì các dòng trong bảng Orders với cùng giá
trị trên cột CustomerID cũng sẽ bị xóa theo.
Thông thường, bạn không nên đánh dấu chọn hai hộp kiểm này. Vì nếu chọn, cơ sở
dữ liệu sẽ tạo ra các thay đổi không mong muốn lên các dòng trong bảng con khi bạn
cập nhật các thay đổi từ DataSet vào cơ sở dữ liệu.
Trang 337
Giáo trình lập trình cơ sở dữ liệu
9.9.2. Quản lý việc cập nhật và xóa dùng các thuộc tính UpdateRule và
DeleteRule của đối tượng ForeignKeyConstraint
Ngoài cách dùng SQL Server, ta cũng có thể điểu khiển cách xử lý việc cập nhật và
xóa dữ liệu trên các bảng có quan hệ bằng cách dùng các thuộc tính UpdateRule và
DeleteRule của đối tượng ForeignKeyConstraint. Những thuộc tính này có kiểu enum
System.Data.Rule. Các hằng số của enum này được liệt kê trong bảng sau:
CONSTANT DESCRIPTION
Cascade Chỉ ra rằng, việc xóa hay cập nhật các DataRow trong DataTable
cha sẽ làm cho các DataRow trong DataTable con cũng bị xóa hoặc
thay đổi theo. Đây là giá trị mặc định.
None Cho biết không có xử lý nào khác được thực hiện.
SetDefault Chỉ ra rằng, giá trị trên các DataColumn trong DataTable con sẽ
được gán giá trị mặc định đã được thiết lập trong thuộc tính
DefaultValue của DataColumn.
SetNull Chỉ ra rằng, giá trị của các DataColumn trong DataTable con sẽ
được gán giá trị DBNull.
Bảng 9.4. Enum System.Data.Rule
Trang 338
Giáo trình lập trình cơ sở dữ liệu
Theo mặc định, thuộc tính UpdateRule được gán là Cascade. Vì thế, khi một thay
đổi được tạo ra trên DataColumn nào đó có liên quan đến ràng buộc khóa ngoại
ForeignKeyConstraint của DataTable cha thì các thay đổi đó cũng được áp dụng cho
các DataRow tương ứng trong DataTable con. Bạn cần gán giá trị None cho thuộc tính
UpdateRule để tránh gặp phải rắc rối xảy ra khi cập nhật các thay đổi vào cơ sở dữ liệu.
Tương tự, thuộc tính DeleteRule cũng được gán giá trị mặc định là Cascade. Do đó,
khi một DataRow trong DataTable cha bị xóa, tất cả các DataRow tương ứng (hay có
quan hệ) trong DataTable con cũng bị xóa theo. Điều này thật sự hữu ích. Tuy nhiên,
cần lưu ý, nếu bạn không dùng giá trị Cascade thì cần phải cập nhật các dòng bị xóa
trong bảng con trước khi cập nhật các dòng bị xóa trong bảng cha.
9.9.3. Cập nhật giá trị khóa chính của bảng cha
Để tìm hiểu các vấn đề xảy ra khi cố gắng thay đổi giá trị khóa chính của một dòng
trong bảng cha có liên quan đến nhiều dòng trong bảng con, ta xét một tình huống sau:
- Có một dòng trong bảng Customers với giá trị trên cột CustomerID là J6COM.
Bản sao của dòng này được lưu trong một DataTable có tên customersDT.
- Trong bảng Orders cũng có một dòng với giá trị trên cột CustomerID là J6COM.
Bản sao của dòng này được lưu trong một DataTable có tên ordersDT.
- Hai đối tượng DataTable customersDT và ordersDT có quan hệ với nhau bằng
cách dùng đối tượng DataRelation như sau:
DataRelation customersOrdersDataRel =
new DataRelation(
"CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
myDataSet.Relations.Add(
customersOrdersDataRel
);
Bây giờ, có hai lựa chọn cho mục Cascade Update Related Fields của
FK_Orders_Customers là:
Unchecked: nghĩa là các thay đổi giá trị trên cột khóa chính CustomerID trong
bảng Customers không tác động lên bảng Orders. Đây là giá trị mặc định.
Checked: nghĩa là các thay đổi giá trị trên cột khóa chính CustomerID trong bảng
Customers kéo theo các thay đổi tương ứng trên bảng Orders.
Hai lựa chọn này tương đương với hai thiết lập cho thuộc tính UpdateRule của đối
tượng ForeignKeyConstraint trong DataRelation đã tạo ra ở trên.
Cascade: nghĩa là các thay đổi trên DataColumn CustomerID của customersDT
kéo theo sự thay đổi tương ứng trên ordersDT. Đây là giá trị mặc định.
Trang 339
Giáo trình lập trình cơ sở dữ liệu
None: nghĩa là các thay đổi trên DataColumn CustomerID của customer
customersDT không ảnh hưởng lên ordersDT.
Ta xét ba trường hợp quan trọng nhất khi thay đổi việc chọn hộp kiểm Cascade
Update Related Fields và gán giá trị thuộc tính UpdateRule là Cascade, sau đó là None.
Lệnh UPDATE này sử dụng tính đồng thời “Last One Wins” vì chỉ có cột khóa
chính OrderID được sử dụng trong mệnh đề WHERE. Như đã nói trong chương trước,
tính đồng thời “Last One Wins” không tối ưu vì người dùng có thể ghi đè các thay đổi
tạo ra bởi người dùng khác.
Thay vào đó, nếu bạn dùng thêm cột CustomerID trong mệnh đề WHERE của lệnh
UPDATE như sau:
ordersUpdateCommand.CommandText =
"UPDATE Orders " +
"SET " +
" CustomerID = @NewCustomerID " +
"WHERE OrderID = @OldOrderID " +
"AND CustomerID = @OldCustomerID";
Khi đó, việc cập nhật các thay đổi vào cơ sở dữ liệu sẽ thất bại vì dòng ban đầu
trong bảng Orders không được tìm thấy. Sở dĩ dòng này không được tìm thấy vì cột
CustomerID đã tự động bị thay đổi từ J6COM thành J7COM trong bảng Orders bởi cơ
sở dữ liệu do tùy chọn Cascade Update Related Fields được chọn cho khóa ngoại trong
bảng Orders. Trong khi đó, giá trị cũ của CustomerID trong ordersDT vẫn được gán là
J6COM. Vì thế, việc thêm đoạn “OrderID = @OldOrderID” trong mệnh đề WHERE
làm cho chương trình không tìm thấy dòng cần thay đổi. Trong trường hợp này, lệnh
UPDATE sẽ phát sinh ra một ngoại lệ DBConcurrencyException.
Trang 340
Giáo trình lập trình cơ sở dữ liệu
Nếu giá trị của CustomerID trong customersDT bị thay đổi từ J6COM thành J7COM
và thay đổi này được cập nhật vào cơ sở dữ liệu thì lệnh UPDATE sẽ phát sinh ra một
ngoại lệ SqlException. Sở dĩ xảy ra điều này là vì bảng con Order hiện tại đang chứa
một dòng với giá trị trên cột CustomerID là J6COM và quan hệ khóa ngoại không thể
làm thay đổi giá trị cột CustomerID trong bảng cha Customers. Thậm chí nếu bạn cố
tình thay đổi giá trị của CustomerID trong ordersDT trước rồi cập nhật các thay đổi vào
cơ sở dữ liệu thì chương trình vẫn phát sinh ra cùng lỗi.
Nếu bạn cố tình thay đổi giá trị trên cột CustomerID của customersDT từ J6COM
thành J7COM thì một ngoại lệ InvalidConstraintException sẽ được phát sinh. Xảy ra
điều này là vì DataTable con ordersDT đang chứa một DataRow với CustomerID là
J6COM và quan hệ khóa ngoại không thể thay đổi giá trị của CustomerID trong
DataTable cha customersDT. Thậm chí nếu bạn cố tính thay đổi CustomerID trong
ordersDT trước thì vẫn nhận được cùng một lỗi.
Trang 341
Giáo trình lập trình cơ sở dữ liệu
Cách đơn giản nhất là xóa bỏ các dòng trong bảng con trước, sau đó cập nhật giá trị
khóa chính của dòng trên bảng cha và cuối cùng là tạo lại các dòng trong bảng con
tương ứng với giá trị khóa chính mới.
Qua chương này, bạn đã đi sâu vào tìm hiểu chi tiết cách tạo và sử dụng các đối
tượng UniqueConstraint, ForeignKeyConstraint để tạo ràng buộc cho các DataTable.
Bạn cũng đã học cách để định nghĩa một quan hệ giữa hai DataTable bằng cách dùng
đối tượng DataRelation. Khi tạo một quan hệ bằng DataRelation, một đối tượng
UniqueConstraint và một đối tượng ForeignKeyConstraint cũng được tự động tạo ra.
Đối tượng UniqueConstraint được thêm vào DataTable cha và đối tượng
ForeignKeyConstraint được thêm vào DataTable con.
Ta cũng đã tìm hiểu cách điều hướng các dòng trong các đối tượng DataTable có
quan hệ với nhau, tạo ra các thay đổi trên đối tượng này và sau đó, cập nhật các thay đổi
vào cơ sở dữ liệu. Trong quá trình cập nhật, cần phải lưu ý một số vấn đề không mong
muốn có thể xảy ra do các lựa chọn liên quan tới ràng buộc từ cơ sở dữ liệu hoặc từ các
đối tượng DataTable có gắn ràng buộc toàn vẹn.
Trang 342
Giáo trình lập trình cơ sở dữ liệu
10. CHƯƠNG 10
KIỂM SOÁT GIAO DỊCH NÂNG CAO
Các giao dịch cơ sở dữ liệu là một tập hợp các lệnh truy vấn SQL được gom lai với
nhau. Khi được thực thi, hoặc tất cả các thay đổi tạo ra bởi giao dịch phải được xác
nhận hoặc tất cả phải được hủy bỏ. Về mặt logic, các lệnh SQL được xem là một đơn vị
công việc.
Một ví dụ điển hình về giao dịch cơ sở dữ liệu là chuyển tiền từ một tài khoản này
sang một tài khoản khác. Giao dịch này gồm hai lệnh UPDATE. Một lệnh dùng để rút
tiền từ tài khoản này và một lệnh để chuyển số tiền vừa rút sang tài khoản khác. Cả hai
lệnh UPDATE này được xem là một giao dịch duy nhất vì cả hai đều phải được xác
nhận hoặc chúng phải được hủy bỏ. Nếu không, tiền trong tài khoản sẽ bị mất.
Các cơ sở dữ liệu hiện này đều có khả năng quản lý nhiều người dùng và nhiều
chương trình truy xuất đến cơ sở dữ liệu cùng lúc. Mỗi chương trình thực thi nhiều giao
dịch trên cùng một cơ sở dữ liệu. Điều này còn được gọi là các giao dịch đồng thời
(concurrent transaction) vì chúng được thi tại cùng một thời điểm. Phần mềm quản trị
cơ sở dữ liệu phải có khả năng đáp ứng các yêu cầu về các giao dịch đồng thời này cũng
như duy trì được tính toàn vẹn của dữ liệu được lưu trong các bảng. Bạn có thể kiểm
soát các mức độ độc lập (the level of isolation) tồn tại giữa các giao dịch của mình với
các giao dịch khác đang thực thi trong cơ sở dữ liệu.
Chương này đi sâu vào việc phân tích và kiểm soát các giao dịch cao cấp sử dụng
SQL Server và ADO.Net.
Trang 343
Giáo trình lập trình cơ sở dữ liệu
Trong đó:
savepointName: là một chuỗi chứa tên mà bạn muốn gán cho điểm lưu.
savepointVariable: là một biến của T-SQL chứa tên của điểm lưu. Biến này phải
có kiểu char, varchar, nchar hoặc nvarchar.
Ví dụ:
Tạo một điểm lưu có tên SaveCustomer
SAVE TRANSACTION SaveCustomer
Các bước để tạo một điểm lưu trong giao dịch cơ sở dữ liệu.
- B1. Bắt đầu giao dịch
- B2. Thêm một dòng vào bảng Customers với CustomerID = ‘J8COM’
- B3. Tạo một điểm lưu
- B4. Thêm một dòng mới vào bảng Orders với CustomerID = ‘J8COM’
- B5. Quay ngược giao dịch trở lại điểm lưu để hủy bỏ việc thực hiện lệnh
INSERT trong bước trước 4 nhưng vẫn giữ lại lệnh INSERT trong bước 2.
Trang 344
Giáo trình lập trình cơ sở dữ liệu
- B6. Xác nhận giao dịch: nghĩa là xác nhận dữ liệu được chèn vào bảng
Customers.
- B7. Lấy dòng mới từ bảng Customers.
- B8. Cố gắng lấy dòng đã bị hủy ở bước 5 trong bảng Orders.
- B9. Xóa dòng mới trong bảng Customers.
Chương trình 10.1: Tạo điểm lưu (savepoint) bởi lệnh T-SQL
/*
Savepoint.sql illustrates how to use a savepoint
*/
USE Northwind
BEGIN TRANSACTION
COMMIT TRANSACTION
Trang 345
Giáo trình lập trình cơ sở dữ liệu
Sau đó, hủy bỏ mọi thay đổi được tạo ra trong các dòng của cơ sở dữ liệu bởi các
lệnh SQL nằm sau điểm lưu này bằng cách gọi phương thức Rollback của
mySqlTransaction. Phương thức này nhận một tham số chứa tên của điểm lưu mà giao
dịch muốn quay lại đó. Chẳng hạn:
mySqlTransaction.Rollback("SaveCustomer");
Chương trình sau minh họa cách tạo một điểm lưu trong giao dịch dùng đối tượng
SqlTransaction. Mã lệnh được viết theo các bước sau:
- B1. Tạo một đối tượng SqlTransaction tên là mySqlTransaction.
- B2. Tạo một đối tượng SqlCommand và gán đối tượng mySqlTransaction cho
thuộc tính Transaction của SqlCommand.
- B3. Thêm một dòng vào bảng Customers.
- B4. Tạo một điểm lưu bằng cách gọi phương thức Save của mySqlTransaction.
Đặt tên cho điểm lưu là SaveCustomer qua tham số của phương thức Save.
- B5. Thêm một dòng vào bảng Orders.
- B6. Quay trở lại điểm lưu ở bước 4. Nghĩa là hủy bỏ việc thực hiện lệnh INSERT
trong bước 5 nhưng vẫn giữa lại những thay đổi được thực hiện bởi lệnh
INSERT ở bước 3.
- B7. Hiển thị dòng mới được thêm vào bảng Customers
- B8. Xóa dòng mới từ bảng Customers.
- B9. Xác nhận giao dịch.
Chương trình 10.2: Tạo điểm lưu dùng phương thức Save của SqlTransaction
/*
Savepoint.cs illustrates how to set a savepoint in a transaction
*/
using System;
using System.Data;
using System.Data.SqlClient;
class Savepoint
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlConnection.Open();
Trang 346
Giáo trình lập trình cơ sở dữ liệu
SqlTransaction mySqlTransaction =
mySqlConnection.BeginTransaction();
mySqlCommand.CommandText =
"INSERT INTO Customers ( CustomerID, CompanyName ) " +
"VALUES ( 'J8COM', 'J8 Company' )";
mySqlTransaction.Save("SaveCustomer");
mySqlCommand.CommandText =
"INSERT INTO Orders ( CustomerID ) " +
"VALUES ( 'J8COM' )";
numberOfRows = mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows inserted = " + numberOfRows);
mySqlTransaction.Rollback("SaveCustomer");
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'J8COM'";
Trang 347
Giáo trình lập trình cơ sở dữ liệu
mySqlDataReader["CompanyName"]);
}
mySqlDataReader.Close();
mySqlCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = 'J8COM'";
numberOfRows = mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows deleted = " + numberOfRows);
mySqlConnection.Close();
}
}
Trang 348
Giáo trình lập trình cơ sở dữ liệu
“phantom” (ảo ảnh hay bóng ma) vì với giao dịch 1, dường như dòng này được
tạo ra như dữ liệu “ma”.
Nonrepeatable reads: giao dịch 1 đọc một dòng và giao dịch 2 cập nhật giá trị
cho dòng mà giao dịch 1 vừa đọc. Sau đó, giao dịch 1 đọc lại dòng đó và phát
hiện ra có sự khác biệt với dòng mà nó đã đọc lúc trước. Điều này được gọi là
“nonrepeatable read” vì dòng ban đầu được đọc bởi giao dịch 1 đã bị thay đổi.
Dirty reads: giao dịch 1 cập nhật một dòng nhưng không xác nhận các thay đổi.
Giao dịch 2 đọc dòng đã cập nhật. Sau đó, giao dịch 1 thực hiện hủy bỏ các thay
đổi trước đó. Như vậy, dòng vừa được đọc bởi giao dịch 2 không còn hợp lệ
(không còn giá trị) – gọi là dirty – vì thay đổi tạo ra bởi giao dịch 1 không được
xác nhận khi dòng đó được đọc bởi giao dịch 2.
Để giải quyết các vấn đề tiềm tàng này, cơ sở dữ liệu sử dụng và thực thi nhiều mức
độ độc lập giao dịch để ngăn chặn các giao dịch đồng thời khỏi đụng độ (interfering)
với nhau. Chuẩn SQL định nghĩa 4 mức độc lập như được liệt kê trong Bảng 10.3. Các
mức độ độc lập này được liệt kê theo thứ tự tăng dần.
ISOLATION DESCRIPTION
LEVEL
READ UNCOMMITTED Cho phép Phantoms, nonrepeatable reads, and dirty reads.
READ COMMITTED Cho phép Phantoms và nonrepeatable reads còn dirty reads
thì không. Đây là mức mặc định của SQL Server.
REPEATABLE READ Cho phép Phantoms. Nonrepeatable và Dirty reads thì không.
SERIALIZABLE Phantoms, nonrepeatable reads, and dirty reads đều bị cấm.
Đây là mức mặc định cho chuẩn SQL.
Bảng 10.3. Các mức độ độc lập giao dịch
SQL Server hỗ trợ tất cả các mức độ độc lập giao dịch này. Mức mặc định được định
nghĩa bởi chuẩn SQL là SERIALIZABLE nhưng mức mặc định của SQL Server là
READ COMMITED và được chấp nhận với hầu hết các ứng dụng.
Khi đặt mức độ độc lập của giao dịch là SERIALIZABLE, bất kỳ dòng nào được
truy xuất trong một giao dịch đến trước đều bị “khóa” (locked) nghĩa là giao dịch khác
không thể thực hiện việc cập nhật cho các dòng đó. Thậm chí các dòng nhận được bởi
lệnh SELECT cũng bị khóa. Bạn phải xác nhận hoặc hủy bỏ giao dịch để giải phóng
“khóa” và cho phép các giao dịch khác truy cập tới các dòng đó. Chỉ nên sử dụng
SERIALIZABLE khi bạn phải bảo đảm giao dịch của mình bị cách ly hoàn toàn khỏi
các giao dịch khác.
Ngoài ra, ADO.Net cũng hỗ trợ một số mức độ độc lập giao dịch. Các giá trị này
được định nghĩa trong enum System.Data.IsolationLevel.
ISOLATION LEVEL DESCRIPTION
Chaos Chờ các thay đổi không thể ghi đè từ các giao dịch độc
lập. SQL Server không hỗ trợ mức độ độc lập này.
ReadCommitted Cho phép Phantoms và nonrepeatable, nhưng dirty reads thì
không. Đây là giá trị mặc định.
ReadUncommitted Cho phép Phantoms, nonrepeatable reads, and dirty reads.
Trang 349
Giáo trình lập trình cơ sở dữ liệu
10.3.1. Thiết lập mức độ độc lập giao dịch dùng T-SQL
Để đặt mức độ độc lập của giao dịch trong T-SQL, ta dùng lệnh SET
TRANSACTION ISOLATION LEVEL. Cú pháp của lệnh này như sau:
SET TRANSACTION ISOLATION LEVEL {
READ COMMITTED |
READ UNCOMMITTED |
REPEATABLE READ |
SERIALIZABLE
}
Chẳng hạn, để đặt mức độ độc lập của giao dịch là SERIALIZABLE, ta viết:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
Mức độc lập của giao dịch được gán cho toàn phiên làm việc. Do đó, nếu ta thực
hiện nhiều giao dịch trong cùng một phiên làm việc, mọi giao dịch sẽ có cùng một mức
độ độc lập. Để thay đổi mức độc lập trong phiên làm việc, đơn giản chỉ cần thực thi lại
lệnh SET TRANSACTION ISOLATION LEVEL với một giá trị mới. Tất cả các giao
dịch sau này trong phiên làm việc sẽ dùng mức độc lập mới.
Ví dụ: Để thay đổi mức độ độc lập sang READ COMMITTED, ta viết:
Đoạn mã sau minh họa cách dùng T-SQL để thiết lập mức độ độc lập của giao dịch
là SERIALIZABLE và thực thi một giao dịch. Sau đó, đặt lại mức độc lập là READ
COMMITTED và thực thi một giao dịch khác.
Chương trình 10.3: Gán mức độc lập của giao dịch bởi lệnh T-SQL
/*
TransactionIsolation.sql illustrates how to set the
transaction isolation level
*/
USE Northwind
BEGIN TRANSACTION
Trang 350
Giáo trình lập trình cơ sở dữ liệu
FROM Customers
WHERE CustomerID IN ('ALFKI', 'J8COM')
UPDATE Customers
SET CompanyName = 'Widgets Inc.'
WHERE CustomerID = 'ALFKI'
COMMIT TRANSACTION
BEGIN TRANSACTION
UPDATE Customers
SET CompanyName = 'Alfreds Futterkiste'
WHERE CustomerID = 'ALFKI'
COMMIT TRANSACTION
10.3.2. Gán mức độ độc lập giao dịch qua đối tượng SqlTransaction
Để tạo một đối tượng SqlTransaction, ta gọi phương thức BeginTransaction() của
đối tượng SqlConnection. Phương thức này có nhiều dạng:
SqlTransaction BeginTransaction()
SqlTransaction BeginTransaction(IsolationLevel myIsolationLevel)
SqlTransaction BeginTransaction(string transactionName)
SqlTransaction BeginTransaction(IsolationLevel myIsolationLevel,
string transactionName)
Trong đó:
myIsolationLevel: là mức độc lập của giao dịch. Tham số này nhận một trong
các giá trị từ enum System.Data.IsolationLevel được liệt kê trong Bảng 10.4.
transactionName: là một chuỗi chứa tên mà bạn muốn gán cho giao dịch
Ví dụ:
Giả sử bạn có một đối tượng SqlConnection có tên mySqlConnection được kết nối
tới cơ sở dữ liệu Northwind của SQL Server. Đoạn mã sau tạo đối tượng SqlTransaction
tên là serializableTrans bằng cách gọi phương thức BeginTransaction() của đối tượng
SqlConnection. Phương thức này nhận 1 tham số có giá trị IsolationLevel.Serializable
Trang 351
Giáo trình lập trình cơ sở dữ liệu
SqlTransaction serializableTrans =
mySqlConnection.BeginTransaction(IsolationLevel.Serializable);
Tiếp theo, ta tạo một đối tượng SqlCommand tên là serializableCommand và gán giá
trị thuộc tính Transaction của nó là serializableTrans
SqlCommand serializableCommand =
mySqlConnection.CreateCommand();
serializableCommand.Transaction = serializableTrans;
Khi đó, bất cứ lệnh SQL nào được thực hiện bởi serializableCommand sẽ sử dụng
serializableTrans và do đó, lệnh sẽ được thực hiện trong một giao dịch
SERIALIZABLE. Đoạn mã sau minh họa việc thực hiện lệnh INSERT để thêm một
dòng vào bảng Customers.
serializableCommand.CommandText =
"INSERT INTO Customers ( CustomerID, CompanyName ) " +
"VALUES ( 'J8COM', 'J8 Company' )";
Và một lệnh UPDATE để cập nhật dữ liệu một dòng của bảng Customers.
serializableCommand.CommandText =
"UPDATE Customers "+
"SET CompanyName = 'Widgets Inc.' "+
"WHERE CustomerID = 'ALFKI'";
numberOfRows = serializableCommand.ExecuteNonQuery();
Cuối cùng, xác nhận các lệnh INSERT và UPDATE bằng cách gọi phương thức
Commit của đối tượng SqlTransaction.
serializableTrans.Commit();
Chương trình dưới đây minh họa cách gán mức độ độc lập của giao dịch bằng cách
dùng đối tượng Transaction. Đoạn mã này có chứa các phương thức sau:
DisplayRows(): Lấy và hiển thị các dòng trong bảng Customers có giá trị trên cột
CustomerID là ALFKI hoặc J8COM.
PerformSerializableTransaction(): tạo một đối tượng SqlTransaction với mức
độc lập là Serializable và sử dụng nó để thực hiện các lệnh INSERT và
UPDATE.
PerformReadCommittedTransaction(): tạo một đối tượng SqlTransaction với
mức độc lập là ReadCommitted và dùng nó để thực hiện hai lệnh UPDATE và
DELETE.
Chương trình 10.4: Gán mức độc lập của giao dịch dùng đối tượng Transaction
/*
TransactionIsolation.cs illustrates how to set the
transaction isolation level
Trang 352
Giáo trình lập trình cơ sở dữ liệu
*/
using System;
using System.Data;
using System.Data.SqlClient;
class TransactionIsolation
{
public static void DisplayRows( SqlCommand mySqlCommand )
{
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID IN ('ALFKI', 'J8COM')";
SqlTransaction serializableTrans =
mySqlConnection.BeginTransaction(IsolationLevel.Serializable);
SqlCommand serializableCommand =
mySqlConnection.CreateCommand();
serializableCommand.Transaction = serializableTrans;
DisplayRows(serializableCommand);
serializableCommand.CommandText =
"INSERT INTO Customers ( " +
"CustomerID, CompanyName " +
") VALUES ( " +
" 'J8COM', 'J8 Company' " +
Trang 353
Giáo trình lập trình cơ sở dữ liệu
")";
serializableCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Widgets Inc.' " +
"WHERE CustomerID = 'ALFKI'";
numberOfRows = serializableCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows updated = " + numberOfRows);
DisplayRows(serializableCommand);
SqlTransaction readCommittedTrans =
mySqlConnection.BeginTransaction(IsolationLevel.ReadCommitted);
SqlCommand readCommittedCommand =
mySqlConnection.CreateCommand();
readCommittedCommand.Transaction = readCommittedTrans;
readCommittedCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Alfreds Futterkiste' " +
"WHERE CustomerID = 'ALFKI'";
readCommittedCommand.CommandText =
Trang 354
Giáo trình lập trình cơ sở dữ liệu
numberOfRows = readCommittedCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows deleted = " + numberOfRows);
DisplayRows(readCommittedCommand);
readCommittedTrans.Commit();
}
PerformSerializableTransaction(mySqlConnection);
PerformReadCommittedTransaction(mySqlConnection);
mySqlConnection.Close();
}
}
In PerformSerializableTransaction()
mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Alfreds Futterkiste
mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Widgets Inc.
mySqlDataReader["CustomerID"] = J8COM
mySqlDataReader["CompanyName"] = J8 Company
In PerformReadCommittedTransaction()
Setting CompanyName to 'Alfreds Futterkiste' for row with CustomerID
of ALFKI
Number of rows updated = 1
mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Alfreds Futterkiste
Trang 355
Giáo trình lập trình cơ sở dữ liệu
Trang 356
Giáo trình lập trình cơ sở dữ liệu
Trang 357
Giáo trình lập trình cơ sở dữ liệu
Hình 10.3. Xem các khóa dùng Lock/Process ID của SQL Server EM
Nút Locks/Process ID hiển thị các khóa được đặt cho mỗi tiến trình. Mỗi tiến trình
có một số SPID được gán bởi SQL Server để xác định tiến trình đó.
Nút Locks/Object cho biết các khóa được đặt trên mỗi tài nguyên bởi tất cả các tiến
trình.
Giả sử bạn đã chạy giao dịch sau (dùng Query Analyzer) với lệnh T-SQL:
USE Northwind
BEGIN TRANSACTION
UPDATE Customers
SET CompanyName = 'Widgets Inc.'
WHERE CustomerID = 'ALFKI'
Lệnh này đặt một khóa Shared lên cơ sở dữ liệu Northwind và một số khóa lên bảng
Customers (có thể xem bởi Enterprise Manager). Hình 10.3 hiển thị các khóa này trong
mục Locks/Process ID của Enterprise Manager.
Trong hình này, tiến trình mang số hiệu SPID 51 tương ứng với Query Analyzer
đang thực thi các lệnh T-SQL. Ở khung bên phải hiển thị một số khóa được đặt cho các
lệnh T-SQL này.
Thông tin trong khung bên phải của Hình 10.3 cho biết các khóa, mỗi thông tin này
được chia làm nhiều cột.
Object: đối tượng đang bị khóa
Lock Type: loại khóa tương ứng với một trong các loại khóa ở Bảng 10.5
Mode: chế độ khóa tương ứng với các chế độ trong Bảng 10.6
Trang 358
Giáo trình lập trình cơ sở dữ liệu
Status: trạng thái khóa có giá trị là GRANT (khóa đã được cấp phát thành công),
CNVT (khóa đã được chuyển đổi) hoặc WAIT (chờ thiết lập khóa).
Owner: đối tượng sở hữu khóa, có giá trị là Sess (khóa phiên làm việc) hoặc Xact
(khóa giao dịch).
Index: là tên của chỉ mục bị khóa (nếu có)
Resource: bộ định danh tài nguyên của đối tượng bị khóa (nếu có).
Sau đó, nếu ta cố gắng thực hiện việc cập nhật trên cùng dòng đó mà không chờ cho
giao dịch trước kết thúc bằng cách dùng lệnh T-SQL:
USE Northwind
BEGIN TRANSACTION
UPDATE Customers
SET CompanyName = 'Alfreds Futterkiste'
WHERE CustomerID = 'ALFKI'
Thì lệnh UPDATE này sẽ chờ cho đến khi giao dịch đầu tiên được xác nhận hoặc bị
quay lại. Hình 10.4 cho thấy hai giao dịch đang được thực thi bởi Query Analyzer. Giao
dịch đầu được hiển thị trong phần bên trái, và đang chặn giao dịch ở bên phải.
Trang 359
Giáo trình lập trình cơ sở dữ liệu
Hình 10.5. Giao dịch 2 tiếp tục khi giao dịch 1 kết được xác nhận
Nếu một lệnh SQL phải chờ hơn 1 giây, SQL Server sẽ trả về một lỗi và tự động hủy
bỏ lệnh SQL này.
Trong C#, ADO.Net cũng cho phép thiết lập thời gian TimeOut bằng cách thực thi
lệnh SET LOCK_TIMEOUT như ví dụ sau:
mySqlCommand.CommandText = "SET LOCK_TIMEOUT 1000";
mySqlCommand.ExecuteNonQuery();
Trang 360
Giáo trình lập trình cơ sở dữ liệu
khác thì giao dịch thứ 2 này sẽ bị chặn. Giao dịch 2 bị chặn vì các giao dịch Serializable
khóa các dòng nhận được và do đó, giao dịch 2 không thể lấy được khóa trên dòng đó.
Chương trình sau minh họa một lệnh Seriablizable để khóa các dòng mà giao dịch 1
nhận được ngăn chặn giao dịch 2 lấy các khóa để cập nhật dữ liệu lên các dòng đó. Giao
dịch 2 có thời gian TimeOut là một giây. Khi thời gian chờ của giao dịch 2 vượt quá 1
giây, chương trình sẽ phát ra lỗi SqlException.
using System;
using System.Data;
using System.Data.SqlClient;
class Block
{
public static void DisplayRows( SqlCommand mySqlCommand )
{
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID IN ('ALFKI', 'J8COM')";
SqlConnection rcConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
serConnection.Open();
rcConnection.Open();
Trang 361
Giáo trình lập trình cơ sở dữ liệu
SqlTransaction serializableTrans =
serConnection.BeginTransaction(IsolationLevel.Serializable);
SqlCommand serializableCommand =
serConnection.CreateCommand();
serializableCommand.Transaction = serializableTrans;
DisplayRows(serializableCommand); // ******************
SqlTransaction readCommittedTrans =
rcConnection.BeginTransaction(IsolationLevel.ReadCommitted);
SqlCommand readCommittedCommand =
rcConnection.CreateCommand();
readCommittedCommand.Transaction = readCommittedTrans;
try
{
// insert a new row into the Customers table
Console.WriteLine("Inserting new row into Customers table " +
"with CustomerID of J8COM");
readCommittedCommand.CommandText =
"INSERT INTO Customers ( " +
"CustomerID, CompanyName " +
") VALUES ( " +
" 'J8COM', 'J8 Company' " +
")";
Trang 362
Giáo trình lập trình cơ sở dữ liệu
readCommittedCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Widgets Inc.' " +
"WHERE CustomerID = 'ALFKI'";
numberOfRows = readCommittedCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows updated = " +
numberOfRows);
catch (SqlException e)
{
Console.WriteLine(e);
}
finally
{
serConnection.Close();
rcConnection.Close();
}
}
}
Nếu biên dịch và chạy chương trình này, nó sẽ phát sinh ra một lỗi SqlException.
Điều này nằm trong dự kiến vì nó cho thấy việc cố gắn lấy khóa đã vượt thời gian chờ
cho phép. Nếu bạn che đi lời gọi hàm DisplayRows() đầu tiên [dòng được đánh dấu
***], chương trình sẽ không phát ra lỗi SqlException. Sở dĩ có điều này là vị việc che đi
lời gọi hàm DisplayRows() sẽ chặn giao dịch Serializable khỏi việc lấy dữ liệu và khóa
các dòng. Sau đó, giao dịch 2 có thể lấy khóa trên dòng ALFKI.
Kết quả chạy chương trình:
mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Alfreds Futterkiste
Inserting new row into Customers table with CustomerID of J8COM
System.Data.SqlClient.SqlException: Lock request time out period
exceeded.
at System.Data.SqlClient.SqlConnection.OnError(SqlException
exception,
TdsParserState state)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException
exception,
TdsParserState state)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior run, SqlCommand
cmdHandler,
SqlDataReader dataStream)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
Trang 363
Giáo trình lập trình cơ sở dữ liệu
at Block.Main()
10.8. Deadlocks
Một deadlock xảy ra khi có hai giao dịch cùng chờ khóa mà một giao dịch khác
đang chiếm giữ. Xét hai giao dịch sau:
Lưu ý rằng, cả hai giao dịch T1 và T2 đều cập nhật trên cùng dòng của bảng
Customers và Products. Nếu T1 và T2 được thực thi tại hai thời điểm khác nhau: T1
thực thi xong rồi đến T2 thực thi thì sẽ không có vấn đề gì. Tuy nhiên, nếu T1 và T2 đều
được thực thi tại cùng một thời điểm với các lệnh UPDATE, khi đó sẽ xảy ra tình trạng
deadlock.
Xét một ví dụ qua các bước sau:
- B1. T1 bắt đầu
- B2. T2 bắt đầu.
- B3. T1 khóa dòng trong bảng Customers và cập nhật dòng đó
- B4. T2 khóa dòng trong bảng Products và cập nhật dòng đó
- B5. T2 chờ lấy khóa (đang giữ bởi T1) trên dòng trong bảng Products
- B6. T1 chờ lấy khóa (đang giữ bởi T2) trên dòng trong bảng Customers
Trong bước 5, T2 đang chờ lấy khóa được nắm giữ bởi T1. Trong bước 6, T1 chờ để
lấy khóa đang được giữ bởi T2. Do đó, xảy ra tình trạng deadlock vì cả hai giao dịch
đang chờ đợi lẫn nhau. Cả hai giao dịch đều nắm giữ các khóa mà giao dịch kia cần.
SQL Server sẽ phát hiện ra deadlock và hủy bỏ một trong hai giao dịch. Giao dịch được
chọn để loại bỏ là giao dịch tốn ít chi phí để hủy bỏ nhất và đồng thời rả về một lỗi cho
biết xảy ra tình trạng deadlock.
Trang 364
Giáo trình lập trình cơ sở dữ liệu
Ta cũng có thể chọn giao dịch để loại bỏ khi xảy ra tình trạng deadlock bằng cách
dùng lệnh T-SQL: SET DEADLOCK_PRIORITY.
Bạn có thể giảm nguy cơ xảy ra deadlock trong chương trình bằng cách tạo ra các
giao dịch càng ngắn càng tốt. Theo cách này, thời gian mà các khóa bị chiếm giữ trên
các đối tượng của cơ sở dữ liệu là ngắn nhất có thể. Bạn cũng nên tạo truy vấn tới các
bảng theo cùng một thứ tự khi thực thi nhiều giao dịch tại cùng một thời điểm. Bằng
cách đó, ta có thể giảm nguy cơ nhiều giao dịch nắm giữ cùng khóa.
Chương trình sau minh họa tình huống deadlock xảy ra khi có hai giao dịch T1 và
T2 cùng nắm giữ khóa như được trình bày ở trên. Mỗi lệnh UPDATE được thực thi
bằng cách dùng một tiến trình riêng để mô phỏng việc cập nhật xen kẽ theo sáu bước ở
trên.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
class Deadlock
{
// create two SqlConnection objects
public static SqlConnection t1Connection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );
Trang 365
Giáo trình lập trình cơ sở dữ liệu
t1Command.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Widgets Inc.' " +
"WHERE CustomerID = 'ALFKI'";
t2Command.CommandText =
"UPDATE Products " +
"SET ProductName = 'Widget' " +
"WHERE ProductID = 1";
t1Command.CommandText =
"UPDATE Products " +
"SET ProductName = 'Chai' " +
"WHERE ProductID = 1";
Trang 366
Giáo trình lập trình cơ sở dữ liệu
t2Command.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Alfreds Futterkiste' " +
"WHERE CustomerID = 'ALFKI'";
t1Connection.Open();
t1Trans = t1Connection.BeginTransaction();
t1Command = t1Connection.CreateCommand();
t1Command.Transaction = t1Trans;
t1Command.CommandText = "SET LOCK_TIMEOUT 5000";
t1Command.ExecuteNonQuery();
t2Connection.Open();
t2Trans = t2Connection.BeginTransaction();
t2Command = t2Connection.CreateCommand();
t2Command.Transaction = t2Trans;
t2Command.CommandText = "SET LOCK_TIMEOUT 5000";
t2Command.ExecuteNonQuery();
Trang 367
Giáo trình lập trình cơ sở dữ liệu
Bạn có thể xem Thread là một tiến trình chạy độc lập với chương trình của mình và
mỗi Thread thực thi các chỉ thị một cách song song với các Thread khác.
Trong chương trình trên có chứa các phương thức sau:
UpdateCustomerT1(): Cập nhật dòng trong bảng Customers có CustomerID là
ALFKI bởi giao dịch 1. Nó gán giá trị cho CompanyName là Widgets Inc.
UpdateProductT2(): cập nhật dòng trong bảng Products có ProductID là 1 bởi
giao dịch 2. Nó gán giá trị cho ProductName là Widget.
UpdateProductT1(): cập nhật dòng trong bảng Products có ProductID là 1 bởi
giao dịch 1. Nó gán giá trị cho ProductName là Chai.
UpdateCustomerT2(): cập nhật dòng trong bảng Customers có CustomerID là
ALFKI bởi giao dịch 2. Nó gán giá trị cho CompanyName là Alfreds Futterkiste.
Những phương thức này được gọi bởi các Thread để thực hiện việc cập nhật xem kẽ
nhau. Chương trình trên cũng cho biết giao dịch 2 sẽ bị hủy bỏ khi có xảy ra deadlock ví
có dùng lệnh SET DEADLOCK_PRIORITY LOW.
Kết quả chạy chương trình
Unhandled Exception:
System.Data.SqlClient.SqlException: Transaction (Process ID 53) was
deadlocked on
{lock} resources with another process and has been chosen as the
deadlock victim.
Trang 368
Giáo trình lập trình cơ sở dữ liệu
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException
exception,
TdsParserState state)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior run, SqlCommand
cmdHandler,
SqlDataReader dataStream)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at Deadlock.UpdateCustomerT2()
Ngày nay, các cơ sở dữ liệu có thể xử lý việc có nhiều người dùng và nhiều chương
trình cùng truy xuất tới cơ sở dữ liệu tại một thời điểm. Mỗi người dùng hay chương
trình đều có khả năng thực hiện các giao diện của mình với cơ sở dữ liệu. Phần mềm
quản trị cơ sở dữ liệu phải có khả năng đáp ứng được các yêu cầu cho tất cả các giao
dịch đồng thời cũng như duy trì được tính toàn vẹn của dữ liệu được lưu trong các bảng.
bạn có thể điều khiển mức độ độc lập của giao dịch đang tồn tại với các giao dịch khác
đang chạy trong cơ sở dữ liệu.
Trong chương này, bạn cũng đi sâu vào việc kiểm soát các giao dịch bằng cách dùng
SQL Server và ADO.Net. Bạn đã học cách thiết lập điểm lưu (savepoint), quay ngược
giao dịch tới điểm lưu và thiết lập mức độ độc lập của giao dịch. Bạn cũng được học về
các chế độ khóa của SQL Server và cách ngăn chặn các giao dịch cũng như làm giảm
nguy cơ xảy ra deadlock bởi các giao dịch khác.
Trang 369
Giáo trình lập trình cơ sở dữ liệu
1. Trần Nguyên Phong, Giáo trình SQL, Đại học Khoa học Huế, 2003
2. Jason Price, Mastering C# Database Programming, Sybex, 2003
Trang 370