You are on page 1of 370

TRƯỜNG ĐẠI HỌC ĐÀ LẠT

KHOA CÔNG NGHỆ THÔNG TIN




NGUYỄN MINH HIỆP


NGUYỄN VĂN PHÚC

Đà Lạt, tháng 06 năm 2009


(Lưu hành nội bộ)
LỜI MỞ ĐẦU

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.

Đà Lạt, tháng 06 năm 2009

Nguyễn Minh Hiệp


Nguyễn Văn Phúc
Giáo trình lập trình cơ sở dữ liệu

MỤC LỤC

LỜI MỞ ĐẦU............................................................................................................ 2
MỤC LỤC ................................................................................................................. 4

1. CHƯƠNG 1: TỔNG QUAN VỀ CƠ SỞ DỮ LIỆU........................................ 11


1.1. Cơ sở dữ liệu và hệ quản trị cơ sở dữ liệu .................................................... 11
1.2. Một số khái niệm ......................................................................................... 12
1.2.1. Khóa chính – Primary Keys .................................................................... 13
1.2.2. Quan hệ và khóa ngoại – Table Relationships and Foreign Keys ............ 13
1.2.3. Giá trị null – Null Values........................................................................ 14
1.2.4. Chỉ mục - Indexes................................................................................... 15
1.2.5. Kiểu dữ liệu – Column Types ................................................................. 16
1.3. Ngôn ngữ truy vấn có cấu trúc (SQL) .......................................................... 17
1.3.1. Các lệnh DML (Data Manipulating Language) ....................................... 17
1.3.1.1. Lấy các dòng từ một hay nhiều bảng .............................................. 18
1.3.1.2. Bổ sung dữ liệu .............................................................................. 25
1.3.1.3. Cập nhật dữ liệu ............................................................................. 26
1.3.1.4. Xóa dữ liệu khỏi một bảng.............................................................. 26
1.3.2. Duy trì tính toàn vẹn của cơ sở dữ liệu ................................................... 27
1.3.3. Gom nhóm các lệnh SQL ....................................................................... 28
1.3.4. Các lệnh định nghĩa dữ liệu - DDL ......................................................... 30
1.3.4.1. Tạo, thay đổi và xóa một bảng........................................................ 30
1.3.4.2. Tạo và xóa chỉ mục (Index) ............................................................ 31
1.3.4.3. Khung nhìn - View ......................................................................... 32
1.3.5. Thủ tục, hàm và trigger........................................................................... 33
1.3.5.1. Thủ tục lưu trữ (Stored Procedure) ................................................. 33
1.3.5.2. Hàm - Function............................................................................... 35
1.3.5.3. Trigger ........................................................................................... 38
1.4. Giao dịch SQL – Transaction SQL............................................................... 41
1.4.1. Mô hình giao dịch trong SQL ................................................................. 42
1.4.2. Giao dịch lồng nhau................................................................................ 44
1.5. Cơ sở dữ liệu Northwind.............................................................................. 46
1.5.1. Bảng Customers ..................................................................................... 47
1.5.2. Bảng Orders ........................................................................................... 48
1.5.3. Bảng Order Details ................................................................................. 49
1.5.4. Bảng Products ........................................................................................ 49
1.6. Sử dụng Microsoft SQL Server .................................................................... 50
1.6.1. Sử dụng Enterprise Manager................................................................... 51
1.6.1.1. Xây dựng các lệnh truy vấn dùng Enterprise Manager .................... 59
1.6.1.2. Tạo bảng dữ liệu............................................................................. 62
1.6.1.3. Thiết lập quyền............................................................................... 64
1.6.1.4. Tạo quan hệ giữa các bảng.............................................................. 65
1.6.1.5. Tạo chỉ mục.................................................................................... 66

Trang 4
Giáo trình lập trình cơ sở dữ liệu

1.6.1.6. Tạo một ràng buộc (constraint) ....................................................... 68


1.6.2. Query Analyzer ...................................................................................... 69
1.6.2.1. Kết nối tới SQL Server ................................................................... 69
1.6.2.2. Tạo và thực thi một lệnh truy vấn SQL ........................................... 70
1.6.2.3. Lưu và mở lại một lệnh SQL .......................................................... 71
1.7. Kết chương .................................................................................................. 71

2. CHƯƠNG 2: TỔNG QUAN VỀ ADO.NET ................................................... 73


2.1. Các lớp kết nối và không kết nối .................................................................. 73
2.2. Các lớp kết nối – The managed provider classes .......................................... 74
2.2.1. Lớp Connection ...................................................................................... 75
2.2.2. Lớp Command........................................................................................ 75
2.2.3. Lớp Parameter ........................................................................................ 75
2.2.4. Lớp ParameterCollection ........................................................................ 75
2.2.5. Lớp đọc dữ liệu tuần tự - DataReader ..................................................... 76
2.2.6. Các lớp điều phối dữ liệu - DataAdapter................................................. 76
2.2.7. Các lớp tạo lệnh truy vấn - CommandBuilder ......................................... 76
2.2.8. Các lớp giao dịch - Transaction .............................................................. 76
2.2.9. Namespace chứa các lớp kết nối ............................................................. 76
2.3. Các lớp không kết nối – Generic Data Classes ............................................. 77
2.3.1. Lớp DataSet ........................................................................................... 77
2.3.2. Lớp DataTable........................................................................................ 77
2.3.3. Lớp DataRow ......................................................................................... 77
2.3.4. Lớp DataColumn .................................................................................... 78
2.3.5. Lớp ràng buộc - Constraint ..................................................................... 78
2.3.5.1. Lớp UniqueConstraint .................................................................... 79
2.3.5.2. Lớp ForeignKeyConstraint ............................................................. 79
2.3.6. Lớp DataView ........................................................................................ 79
2.3.7. Lớp quan hệ - DataRelation .................................................................... 79
2.3.8. Namespace chứa các lớp không kết nối .................................................. 79
2.4. Sử dụng ADO.Net để thực thi một truy vấn SQL ......................................... 79
2.4.1. Các bước thực hiện một truy vấn trong ADO.Net ................................... 80
2.4.2. Chi tiết thực hiện truy vấn bởi ADO.Net ................................................ 80
2.5. Kết hợp Windows Form với ADO.Net ......................................................... 86
2.6. Kết chương .................................................................................................. 93

3. CHƯƠNG 3: KẾT NỐI CƠ SỞ DỮ LIỆU ..................................................... 95


3.1. Lớp SqlConnection ...................................................................................... 95
3.2. Kết nối cơ sở dữ liệu SQL Server bởi SqlConnection................................... 96
3.3. Kết nối tới cơ sở dữ liệu Access và Oracle ................................................... 98
3.3.1. Kết nối cơ sở dữ liệu Access................................................................... 98
3.3.2. Kết nối cơ sở dữ liệu Oracle ................................................................. 100
3.4. Mở và đóng kết nối .................................................................................... 100
3.5. Tổ hợp kết nối – Connection Pooling ......................................................... 102

Trang 5
Giáo trình lập trình cơ sở dữ liệu

3.6. Quản lý trạng thái kết nối........................................................................... 105


3.7. Quản lý sự kiện trong quá trình kết nối ...................................................... 105
3.7.1. Sự kiện StateChange............................................................................. 105
3.7.2. Sự kiện InfoMessage ............................................................................ 107
3.8. Tạo đối tượng kết nối dùng Visual Studio .NET......................................... 109
3.9. Tạo trình xử lý sự kiện bằng Visual Studio .Net......................................... 112
3.10. Kết chương ................................................................................................ 114

4. CHƯƠNG 4: THỰC THI LỆNH TRUY VẤN CƠ SỞ DỮ LIỆU............... 116


4.1. Lớp SqlCommand...................................................................................... 116
4.2. Tạo đối tượng SqlCommand ...................................................................... 118
4.2.1. Tạo đối tượng SqlCommand dùng phương thức khởi tạo...................... 118
4.2.2. Tạo đối tượng SqlCommand bằng phương trức CreateCommand ......... 119
4.3. Thực thi các lệnh SELECT và TableDirect ................................................ 119
4.3.1. Phương thức ExecuteReader................................................................. 120
4.3.2. Điều khiển cách xử lý (Command Behavior) của phương thức
ExecuteReader ................................................................................................. 123
4.3.2.1. Sử dụng CommandBehavior.SingleRow....................................... 123
4.3.2.2. Sử dụng CommandBehavior.SchemaOnly .................................... 125
4.3.3. Thực thi lệnh TableDirect bởi phương thức ExecuteReader.................. 130
4.3.4. Thực thi lệnh SELECT dùng phương thức ExecuteScalar..................... 132
4.3.5. Lấy dữ liệu XML dùng phương thức ExecuteXMLReader ................... 133
4.4. Thực thi các lệnh làm thay đổi thông tin trong cơ sở dữ liệu ...................... 136
4.4.1. Thực thi các lệnh INSERT, UPDATE và DELETE .............................. 136
4.4.1.1. Thêm dòng mới bằng lệnh INSERT.............................................. 136
4.4.1.2. Cập nhật dữ liệu bằng lệnh UPDATE ........................................... 137
4.4.1.3. Xóa dữ liệu bằng lệnh DELETE ................................................... 137
4.4.2. Thực thi lệnh DDL dùng phương thức ExecuteNonQuery .................... 139
4.5. Các giao dịch trong cơ sở dữ liệu............................................................... 142
4.6. Truyền tham số vào các lệnh...................................................................... 144
4.7. Gọi các thủ tục SQL Server........................................................................ 149
4.7.1. Thực thi một Stored Procedure không trả về dữ liệu ............................. 149
4.7.2. Lấy dữ liệu trả về từ tham số dùng từ khóa OUTPUT........................... 150
4.7.3. Lấy dữ liệu trả về bởi lệnh RETURN.................................................... 154
4.7.4. Thực thi một Stored Procedure có trả về tập dữ liệu ............................. 156
4.8. Tạo đối tượng Command bằng Visual Studio .Net ..................................... 160
4.9. Kết chương ................................................................................................ 162

5. CHƯƠNG 5: LỚP DATAREADER.............................................................. 164


5.1. Lớp SqlDataReader.................................................................................... 164
5.2. Tạo đối tượng SqlDataReader .................................................................... 166
5.3. Đọc dữ liệu từ SqlDataReader.................................................................... 167
5.4. Lấy giá trị từ các cột với kiểu cụ thể .......................................................... 170
5.5. Sử dụng các phương thức dạng Get* để đọc dữ liệu................................... 171

Trang 6
Giáo trình lập trình cơ sở dữ liệu

5.5.1. Lấy kiểu dữ liệu của một cột................................................................. 174


5.5.2. Đọc dữ liệu bằng phương thức GetSql* ................................................ 177
5.6. Xử lý giá trị null ........................................................................................ 182
5.7. Thực thi nhiều lệnh truy vấn SQL .............................................................. 183
5.7.1. Thực thi nhiều lệnh SELECT................................................................ 183
5.7.2. Thực thi nhiều lệnh SELECT, INSERT, UPDATE và DELETE........... 186
5.8. Sử dụng DataReader trong Visual Studio .NET ......................................... 188
5.9. Kết chương ................................................................................................ 192

6. CHƯƠNG 6: LƯU TRỮ DỮ LIỆU VỚI DATASET ................................... 193


6.1. Lớp SqlDataAdapter .................................................................................. 193
6.2. Tạo đối tượng SqlDataAdapter................................................................... 196
6.3. Lớp DataSet............................................................................................... 197
6.4. Tạo một đối tượng DataSet ........................................................................ 200
6.4.1. Đưa dữ liệu vào DataSet ....................................................................... 201
6.4.2. Đưa dữ liệu vào nhiều DataTable của một DataSet ............................... 209
6.5. Trộn các DataRow, DataTable và DataSet vào một DataSet khác .............. 216
6.6. Ánh xạ các bảng và các cột ........................................................................ 219
6.7. DataSet có định kiểu – Strongly Typed DataSet......................................... 223
6.7.1. Tạo đối tượng DataSet có định kiểu...................................................... 223
6.7.2. Sử dụng DataSet có định kiểu............................................................... 224
6.8. Tạo đối tượng DataAdapter bằng Visual Studio .Net.................................. 227
6.9. Tạo đối tượng DataSet bằng Visual Studio .Net ......................................... 232
6.10. Kết chương ................................................................................................ 234

7. CHƯƠNG 7: CẬP NHẬT DỮ LIỆU DÙNG DATASET............................. 236


7.1. Lớp DataTable ........................................................................................... 236
7.2. Lớp DataRow ............................................................................................ 238
7.3. Lớp DataColumn ....................................................................................... 239
7.4. Tạo các ràng buộc cho DataTable và DataColumn ..................................... 240
7.4.1. Tạo các ràng buộc bằng cách viết mã.................................................... 241
7.4.2. Tạo ràng buộc cho các DataTable ......................................................... 242
7.4.3. Tạo ràng buộc cho DataColumn ........................................................... 246
7.4.4. Tạo ràng buộc bởi phương thức FillSchema của DataAdapter .............. 251
7.5. Tìm kiếm, trích lọc và sắp xếp các dòng trong DataTable .......................... 256
7.5.1. Tìm kiếm một DataRow từ DataTable .................................................. 256
7.5.2. Trích lọc các DataRow từ DataTable .................................................... 257
7.6. Cập nhật dữ liệu trên các dòng của DataTable............................................ 261
7.6.1. Cấu hình DataAdapter để cập nhật thay đổi vào cơ sở dữ liệu............... 262
7.6.2. Thiết lập thuộc tính InsertCommand..................................................... 262
7.6.3. Thiết lập thuộc tính UpdateCommand .................................................. 262
7.6.4. Thiết lập thuộc tính DeleteCommand ................................................... 264
7.6.5. Thêm một DataRow vào DataTable...................................................... 265
7.6.6. Cập nhật một DataRow trong DataTable .............................................. 267

Trang 7
Giáo trình lập trình cơ sở dữ liệu

7.6.7. Xóa một DataRow ra khỏi DataTable ................................................... 269


7.7. Lấy giá trị mới của các cột định danh......................................................... 270
7.8. Cập nhật dữ liệu dùng Stored Procedure .................................................... 272
7.8.1. Sử dụng SET NOCOUNT ON trong Stored Procedure ......................... 274
7.8.2. Gọi Stored Procedure từ đối tượng DataAdapter................................... 275
7.8.3. Thêm một DataRow vào DataTable...................................................... 277
7.8.4. Cập nhật dữ liệu trên một DataRow của DataTable .............................. 277
7.8.5. Xóa một DataRow khỏi DataTable ....................................................... 278
7.9. Tự động phát sinh truy vấn SQL ................................................................ 279
7.10. Các sự kiện của DataAdapter ..................................................................... 280
7.10.1. Sự kiện FillError................................................................................... 280
7.10.2. Sự kiện RowUpdating .......................................................................... 281
7.10.3. Sự kiện RowUpdated............................................................................ 283
7.11. Các sự kiện của DataTable......................................................................... 283
7.11.1. Sự kiện ColumnChanging và ColumnChanged ..................................... 284
7.11.2. Sự kiện RowChanging và RowChanged ............................................... 285
7.11.3. Sự kiện RowDeleting và RowDeleted................................................... 286
7.12. Xử lý lỗi cập nhật....................................................................................... 286
7.12.1. Kiểm tra lỗi .......................................................................................... 287
7.12.2. Sửa lỗi .................................................................................................. 288
7.13. Sử dụng các giao dịch với DataSet ............................................................. 288
7.13.1. Sử dụng thuộc tính Transaction của các DataAdapter Command .......... 289
7.13.2. Cập nhật dữ liệu trong DataSet có định kiểu ......................................... 289
7.14. Kết chương ................................................................................................ 291

8. CHƯƠNG 8: TRÍCH LỌC VÀ SẮP XẾP VỚI DATAVIEW .................... 293


8.1. Lớp DataView ........................................................................................... 293
8.2. Tạo và sử dụng đối tượng DataView .......................................................... 295
8.3. Sử dụng thuật toán sắp xếp mặc định ......................................................... 298
8.4. Trích lọc nâng cao...................................................................................... 299
8.5. Lớp DataRowView .................................................................................... 299
8.5.1. Tìm kiếm các DataRowView trong DataView ...................................... 300
8.5.2. Tìm chỉ số của một DataRowView dùng phương thức Find.................. 300
8.5.3. Tìm các đối tượng DataRowView bằng phương thức FindRows........... 301
8.5.4. Thêm, cập nhật và xóa các DataRowView từ DataView ....................... 303
8.5.4.1. Thêm một DataRowView vào DataView ...................................... 303
8.5.4.2. Cập nhật một DataRowView đang tồn tại ..................................... 304
8.5.4.3. Xóa một DataRowView đang tồn tại ............................................ 304
8.5.5. Tạo đối tượng DataView con................................................................ 308
8.6. Lớp DataViewManager.............................................................................. 310
8.7. Tạo và sử dụng đối tượng DataViewManager ............................................ 311
8.8. Tạo đối tượng DataView dùng Visual Studio .Net ..................................... 313
8.9. Kết chương ................................................................................................ 315

Trang 8
Giáo trình lập trình cơ sở dữ liệu

9. CHƯƠNG 9: QUAN HỆ VÀ RÀNG BUỘC................................................. 316


9.1. Lớp UniqueConstraint................................................................................ 316
9.2. Tạo đối tượng UniqueConstraint ................................................................ 317
9.3. Lớp ForeignKeyConstraint ........................................................................ 319
9.4. Tạo đối tượng ForeignKeyConstraint ......................................................... 320
9.5. Lớp DataRelation....................................................................................... 322
9.6. Tạo và sử dụng đối tượng DataRelation ..................................................... 322
9.6.1. Tạo đối tượng DataRelation.................................................................. 323
9.6.2. Khảo sát các ràng buộc tạo bởi DataRelation........................................ 325
9.7. Quản lý đối tượng DataRow trong các DataTable cha và con..................... 326
9.7.1. Phương thức GetChildRow................................................................... 327
9.7.2. Phương thức GetParentRow ................................................................. 327
9.8. Thêm, cập nhật và xóa các dòng có quan hệ với nhau ................................ 328
9.8.1. Thêm các đối tượng DataRow .............................................................. 333
9.8.2. Cập nhật và xóa đối tượng DataRow .................................................... 333
9.8.3. Cập nhật các thay đổi vào cơ sở dữ liệu................................................ 334
9.9. Các vấn đề liên quan đến việc cập nhật khóa chính .................................... 336
9.9.1. Điều khiển việc cập nhật và xóa dùng SQL Server ............................... 336
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 ............................................. 338
9.9.3. Cập nhật giá trị khóa chính của bảng cha.............................................. 339
9.9.3.1. Trường hợp thứ nhất..................................................................... 340
9.9.3.2. Trường hợp thứ 2.......................................................................... 341
9.9.3.3. Trường hợp thứ 3.......................................................................... 341
9.9.3.4. Kết luận........................................................................................ 341
9.10. Kết chương ................................................................................................ 342

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

TÀI LIỆU THAM KHẢO................................................................................... 370

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.

Những vấn đề sẽ được đề cập trong chương này


 Giới thiệu về cơ sở dữ liệu
 Một số khái niệm
 Ngôn ngữ truy vấn có cấu trúc
 Khung nhìn (View)
 Thủ tục lưu trữ, hàm và trigger
 Giao dịch cơ sở dữ liệu
 Cách sử dụng Microsoft SQL Server 2000
 Khám phá cơ sở dữ liệu Northwind
 Xây dựng các lệnh truy vấn với Enterprise Manager
 Tạo bảng dữ liệu

1.1. Cơ sở dữ liệu và hệ quản trị cơ sở dữ liệu


Một cơ sở dữ liệu là một tập các thông tin có tổ chức. Cơ sở dữ liệu quan hệ là tập
hợp các thông tin có liên quan với nhau và được tổ chức thành các cấu trúc được gọi là
bảng (Table). Mỗi bảng lại được chia làm nhiều hàng (Rows) và nhiều cột (Columns).
Để truy xuất đến một cơ sở dữ liệu, chúng ta dùng ngôn ngữ truy vấn có cấu trúc (SQL
– Structured Query Language). Đây là một ngôn ngữ chuẩn được hỗ trợ trên hầu hết
các hệ quản trị cơ sở dữ liệu như SQL Server, Access, MySQL hay Oracle.
Chẳng hạn, bảng 1.1 sau minh họa chi tiết vài sản phẩm được bán bởi công ty
Northwind. Bảng này liệt kê mã sản phẩm, tên sản phẩm, số lượng trên đơn vị tính và
giá của 10 sản phẩm đầu tiên. Thông tin này được lấy từ bảng Products trong cơ sở dữ
liệu Northwind.
PRODUCT ID NAME QUANTITY PER UNIT Unit Price
1 Chai 10 boxes x 20 bags $18
2 Chang 24-12oz bottles $19
3 Aniseed Syrup 12-550ml bottles $10
4 Chef Anton's Cajun Seasoning 48-6oz jars $22

Trang 11
Giáo trình lập trình cơ sở dữ liệu

PRODUCT ID NAME QUANTITY PER UNIT Unit Price


5 Chef Anton's Gumbo Mix 36 boxes $21.35
6 Grandma's Boysenberry Spread 12-8oz jars $25
7 Uncle Bob's Organic Dried Pears 12-1lb pkgs. $30
8 Northwoods Cranberry Sauce 12-12oz jars $40
9 Mishi Kobe Niku 18-500g pkgs. $97
10 Ikura 12-200ml jars $31
Bảng 1.1. Một vài mẫu tin trong bảng Products
Bạn có thể lưu trữ thông tin trong một cơ sở dữ liệu trên giấy hoặc ở dạng điện từ
lưu trong bộ nhớ và hệ thống tập tin của máy tính. Hệ thống được dùng để quản lý
thông tin trong cơ sở dữ liệu gọi là hệ quản trị cơ sở dữ liệu. Trong trường hợp cơ sở
dữ liệu điện tử, hệ quản trị cơ sở dữ liệu là phần mểm quản lý thông tin trong bộ nhớ
và các tập tin máy tính. Một số hệ quản trị cơ sở dữ liệu thông dụng là Microsoft SQL
Server, Oracle và DB2.
Cần phải phân biệt sự khác nhau giữa hai khái niệm: cơ sở dữ liệu và hệ quản trị cơ
sở dữ liệu. Một cơ sở dữ liệu là một tập thông tin có cấu trúc còn hệ quản trị cơ sở dữ
liệu là phần mểm cho phép lưu trữ và cung cấp các công cụ để thao tác trên các thông
tin được lưu trữ đó.
Một khái niệm khác cũng cần phải đề cập đến, đó là lược đồ cơ sở dữ liệu
(database schema). Thuật ngữ này dùng để biễu diễn cấu trúc của dữ liệu, bao gồm cả
định nghĩa về các bảng, các cột tạo nên cơ sở dữ liệu.

1.2. Một số khái niệm


Một cơ sở dữ liệu có thể có nhiều bảng và các bảng lại có quan hệ với nhau. Chẳng
hạn, Customers, Orders, Order Details và Products là 4 trong nhiều bảng của cơ sở dữ
liệu Northwind. Hình 1.1 sau minh họa mối quan hệ giữa các bảng này.

Trang 12
Giáo trình lập trình cơ sở dữ liệu

Hình 1.1. Lược đồ quan hệ giữa các bảng.


Các cột của bảng được liệt kê ở các ô bên dưới tên bảng. Chẳng hạn, bảng
Customers có 11 cột: CustomerID, CompanyName, ContactName, ContactTitle,
Address, City, Region, PostalCode, Country, Phone, Fax.

1.2.1. Khóa chính – Primary Keys


Thông thường, mỗi bảng trong cơ sở dữ liệu có một hay nhiều cột xác định duy
nhất một mẫu tin trong bảng. Cột này được gọi là khóa chính của bảng. Một khóa
chính có thể gồm nhiều cột. Trong trường hợp này, nó được gọi là khóa kép
(composite key). Giá trị của khóa chính trên mỗi mẫu tin trong bảng phải là duy nhất.
Chẳng hạn, trong bảng Customers, khóa chính là cột CustomerID. Biểu tượng
chiếc chìa khóa nằm bên trái cột CustomerID chỉ ra rằng cột này là khóa chính của
bảng. Tương tự, khóa chính của bảng Orders là OrderID, của bảng Order Details là
khóa kép gồm OrderId và ProductID, của bảng Products là ProductID.

1.2.2. Quan hệ và khóa ngoại – Table Relationships and Foreign Keys


Đường nối giữa các bảng trong Hình 1.1 cho thấy mối quan hệ giữa các bảng. Biểu
tượng chìa khóa và ký hiệu vô cùng (∞) chỉ ra một mối quan hệ một-nhiều giữa hai
bảng. Nghĩa là, một dòng trong bảng này có quan hệ tới một hay nhiều dòng trong một
bảng khác.
Chẳng hạn, bảng Customers có quan hệ một-nhiều với bảng Orders có nghĩa là một
khách hàng có thể có nhiều đơn đặt hàng. Tương tự, quan hệ một-nhiều giữa hai bảng
Orders và Order Details cho biết một đơn đặt hàng được tạo bởi nhiều chi tiết đặt hàng
(nhiều sản phẩm). Cuối cùng, quan hệ một-nhiều giữa Products và Order Details có
nghĩa là một sản phẩm có thể xuất hiện trong nhiều đơn hàng.
Các quan hệ một-nhiều được tạo nên bởi các khóa ngoại (foreign keys). Chẳng
hạn, bảng Orders có một cột tên là CustomerID. Cột này liên kết tới cột CustomerID
trong bảng Customers thông qua một khóa ngoại. Điều này có nghĩa là mỗi dòng trong
bảng Orders phải có một dòng tương ứng trong bảng Customers với giá trị hai cột
CustomerID phải trùng khới nhau. Nếu một hàng trong bảng Orders có cột
CustomerID là ALFKI thì cũng phải có một dòng trong bảng Customers với cột
CustomerID có giá trị là ALFKI. Vì quan hệ giữa hai bảng Customers và Orders là
một-nhiều nên có thể có nhiều dòng trong bảng Orders có cùng giá trị trên cột
CustomerID. Về mặt khái niệm, có thể xem khóa ngoại như một con trỏ từ bảng
Orders đến bảng Customers.
Thông thường, các bảng chứa khóa ngoại còn được gọi là bảng con và các bảng
chứa cột mà khóa ngoại tham chiếu đến gọi là bảng cha. Chẳng hạn, bảng Orders là
bảng con, còn bảng Customers là bảng cha. Quan hệ khóa ngoại thường được gọi là
quan hệ cha con (parent-child relationships).

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 1.2. Quan hệ giữa hai bảng Orders và Customers

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.

1.2.3. Giá trị null – Null Values


Các cơ sở dữ liệu cũng phải cung cấp khả năng quản lý các giá trị rỗng hay nói
cách khác là giá trị chưa biết. Các giá trị chưa biết được gọi là giá trị null và một cột
có thể được định nghĩa là cho phép hay không cho phép chứa giá trị null. Khi một cột
không cho phép chứa giá trị null, nó được định nghĩa là not-null và ngược lại. Một cột
được định nghĩa là not-null thì giá trị của nó trên mỗi dòng phải luôn tồn tại (phải có

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.

1.2.4. Chỉ mục - Indexes


Khi tìm kiếm một chủ đề nào đó trong một cuốn sách, ta có thể lướt qua toàn bộ
nội dung cuốn sách hoặc có thể sử dụng bảng mục lục để tìm chính xác vị trí của chủ
đề quan tâm. Một chỉ mục (index) trong một bảng của cơ sở dữ liệu cũng tương tự như
khái niệm mục lục của cuốn sách ngoại trừ các chỉ mục cơ sở dữ liệu được sử dụng để
tìm kiếm các dòng cụ thể trong một bảng. Hạn chế của chỉ mục là khi thêm một dòng
mới vào bảng, cần phải có thêm thời gian để cập nhật lại chỉ mục của dòng mới.

Hình 1.3. Các chỉ mục của bảng Customers


Nói chung, chỉ nên tạo chỉ mục trên cột mà bạn muốn tìm kiếm và chỉ nhận 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 tốt khi dùng chỉ mục là nó chỉ
hữu ích trong trường hợp mong muốn một câu truy vấn đơn có thể nhận về không quá
10% toàn bộ số hàng trong một bảng. Điều này có nghĩa là các cột cần làm chỉ mục
phải có một miền giá trị lớn. Cách tốt nhất là dùng chỉ mục cho các cột chứa các giá trị
duy nhất trên mỗi dòng. Điều này được áp dụng cho tất cả các kiểu dữ liệu, không
riêng gì dạng số.

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.

1.2.5. Kiểu dữ liệu – Column Types


Mỗi cột trong một bảng có một kiểu dữ liệu cụ thể. Kiểu này tương tự như kiểu của
một biến trong C# ngoại trừ một kiểu dữ liệu trong cơ sở dữ liệu áp dụng cho loại giá
trị mà bạn có thể lưu trữ trong một cột của bảng. Sau đây là các kiểu dữ liệu được xây
dựng sẵn trong SQL Server.
TYPE DESCRIPTION
bigint Số nguyên có giá trị từ -263 (-9,223,372,036,854,775,808)
tới 263-1 (9,223,372,036,854,775,807).
int Số nguyên có giá trị từ -231 (-2,147,483,648) tới 231-1
(2,147,483,647).
smallint Số nguyên có giá trị từ 215 (-32,768) tới 215-1 (32,767).
Tinyint Số nguyên có giá trị từ 0 tới 255.
bit Chỉ nhận một trong hai giá trị 0 hoặc 1.
Decimal Số thập phân có giá trị từ -1038+1 tới 1038-1.
Numeric Giống kiểu decimal.
money Kiểu tiền tệ có giá trị từ -263 (-922,337,203,685,477.5808)
tới 263-1 (922,337,203,685,477.5807), với độ chính xác tới
1/10000.
smallmoney Kiểu tiền tệ có giá trị từ -214,748.3648 tới 214,748.3647,
với độ chính xác tới 1/10000.
float Số thực có giá trị từ -1.79E+308 tới 1.79E+308.
real Số thực có giá trị từ -3.40E + 38 tới 3.40E + 38.
datetime Kiểu ngày giờ có giá trị từ January 1, 1753, tới December
31, 9999, với độ chính xác tới 3/100 giây (3.33
milliseconds).
smalldatetime Kiểu ngày giờ có giá trị từ January 1, 1900 tới June 6,
2079 với độ chính xác tới 1 phút.
char Kiểu (chuỗi) kí tự chiều dài không đổi, không phải Unicode
với chiều dài tối đa là 8000 kí tự.
Varchar Kiểu (chuỗi) kí tự có chiều dài thay đổi, không phải
Unicode với chiều dài tối đa là 8000 kí tự.
text Kiểu (chuỗi) kí tự có chiều dài thay đổi, không phải
Unicode với chiều dài tối đa 231-1 (2,147,483,647) ký tự.
nchar Kiểu (chuỗi) kí tự Unicode chiều dài không đổi với chiều
dài tối đa là 4000 kí tự.

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

1.3. Ngôn ngữ truy vấn có cấu trúc (SQL)


Phần này trình bày cách sử dụng ngôn ngữ truy vấn có cấu trúc (SQL – Structured
Query Language) để truy xuất cơ sở dữ liệu. Ngoài ra, chúng ta sẽ tìm hiểu hai công
cụ để tạo và thực thi các lệnh truy vấn, đó là Query Analyzer và Visual Studio .Net.
SQL là một ngôn ngữ chuẩn cho phép truy xuất đến 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 truy xuất dữ liệu gì và phải lấy dữ liệu như thế
nào một cách chính xác. Có nhiều loại câu lệnh SQL, tuy nhiên, thông dụng nhất là hai
loại sau:
 DML – Data Manipulation Language
 DDL – Data Definition Language
Các lệnh DML cho phép bạn truy xuất, thêm, hiệu chỉnh và 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 bạn tạo ra các cấu trúc cơ sở
dữ liệu như bảng, khung nhìn,…

1.3.1. Các lệnh DML (Data Manipulating Language)


Các lệnh DML cho phép lấy, thêm, hiệu chỉnh hay xóa các dòng được lưu trong
các bảng của cơ sở dữ liệu. Có 4 lệnh DML thường dùng:
- SELECT: Lấy các dòng từ một hay nhiều bảng
- INSERT: Thêm một hay nhiều dòng mới vào một bảng
- UPDATE: Cập nhật giá trị cho một hay nhiều dòng của bảng
- DELETE: Xóa một hay nhiều dòng khỏi một bảng

Trang 17
Giáo trình lập trình cơ sở dữ liệu

1.3.1.1. Lấy các dòng từ một hay nhiều bảng


Câu lệnh SELECT được sử dụng để truy xuất dữ liệu từ các dòng và các cột của
một hay nhiều bảng, khung nhìn. Câu lệnh này có thể dùng để thực hiện phép chọn
(tức là truy xuất một tập con các dòng trong một hay nhiều bảng), phép chiếu (tức là
truy xuất một tập con các cột trong một hay nhiều bảng) và phép nối (tức là liên kết
các dòng trong hai hay nhiều bảng để truy xuất dữ liệu). Ngoài ra, câu lệnh này còn
cung cấp khả năng thực hiện các thao tác truy vấn và thống kê dữ liệu phức tạp khác.
Cú pháp chung của câu lệnh SELECT có dạng:
SELECT [ALL | DISTINCT][TOP n] danh_sách_chọn
[INTO tên_bảng_mới]
FROM danh_sách_bảng/khung_nhìn
[WHERE điều_kiện]
[GROUP BY danh_sách_cột]
[HAVING điều_kiện]
[ORDER BY cột_sắp_xếp]
[COMPUTE danh_sách_hàm_gộp [BY danh_sách_cột]]

Đ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

 Kiểm tra giới hạn của dữ liệu (BETWEEN/ NOT BETWEEN)


 Danh sách
 Kiểm tra khuôn dạng dữ liệu (dùng ký tự đại diện).

 Các giá trị NULL

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

FROM Customers WHERE Country = 'UK';

Hình 1.5. Kết quả truy vấn có mệnh đề WHERE

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.

SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice


FROM Products
WHERE ProductID = 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

SELECT ProductID, ProductName


FROM Products
WHERE ProductName LIKE 'Cha_';

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.

SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice


FROM Products
WHERE ProductID IN (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

1.3.1.2. Bổ sung dữ liệu


Dữ liệu trong các bảng được thể hiện dưới dạng các dòng (bản ghi). Để bổ sung
thêm các dòng dữ liệu vào một bảng, ta sử dụng câu lệnh INSERT. Hầu hết các hệ
quản trị CSDL dựa trên SQL cung cấp các cách dưới đây để thực hiện thao tác bổ sung
dữ liệu cho bảng:
 Bổ sung từng dòng dữ liệu với mỗi câu lệnh INSERT. Đây là các sử dụng
thường gặp nhất trong giao dịch SQL.
 Bổ sung nhiều dòng dữ liệu bằng cách truy xuất dữ liệu từ các bảng dữ liệu
khác.
Để bổ sung một dòng dữ liệu mới vào bảng, ta sử dụng câu lệnh INSERT với cú
pháp như sau:
INSERT INTO tên_bảng[(danh_sách_cột)]
VALUES (danh_sách_trị)

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ụ 1: Thêm một dòng mới vào bảng Customers


INSERT INTO Customers ( CustomerID, CompanyName, ContactName,
ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax
)
VALUES ('JPCOM', 'Jason Price Company', 'Jason Price', 'Owner', '1
Main Street', 'New York', NULL, '12345', 'USA', '(800)-555-1212',
NULL );

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

INSERT INTO Customers VALUES ( 'CRCOM', 'Cynthia Red Company',


'Cynthia Red', 'Owner', '2 South Street', 'New York', NULL, '12345',
'USA', '(800)-555-1212', NULL );

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:

INSERT INTO Custs


SELECT CustomerID, CompanyName, ContactName, Address
FROM Customers
WHERE Country = 'USA';

1.3.1.3. Cập nhật dữ liệu


Câu lệnh UPDATE trong SQL được sử dụng để cập nhật dữ liệu trong các bảng.
Câu lệnh này có cú pháp như sau:
UPDATE tên_bảng
SET tên_cột = biểu_thức
[, ..., tên_cột_k = biểu_thức_k]
[FROM danh_sách_bảng]
[WHERE điều_kiện]

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';

1.3.1.4. Xóa dữ liệu khỏi một bảng


Để xoá dữ liệu trong một bảng, ta sử dụng câu lệnh DELETE. Cú pháp của câu
lệnh này như sau:
DELETE FROM tên_bảng
FROM danh_sách_bảng ]
[ WHERE điều_kiện ]

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

DELETE FROM Customers


WHERE CustomerID = 'CRCOM';

1.3.2. Duy trì tính toàn vẹn của cơ sở dữ liệu


Phần mềm quản trị cơ sở dữ liệu phải bảo đảm được rằng: thông tin được lưu trong
các bảng phải nhất quán. Điều đó duy trì được tính toàn vẹn của thông tin. Sau đây là
hai trường hợp điển hình:
 Khóa chính của một dòng luôn chứa một giá trị duy nhất.
 Khóa ngoại của một dòng trong bảng con luôn tham chiếu đến một giá trị tồn
tại trong bảng cha.

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.

INSERT INTO Customers ( CustomerID, CompanyName, ContactName,


ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax
)
VALUES ('ALFKI', 'Jason Price Company', 'Jason Price', 'Owner', '1
Main Street', 'New York', NULL, '12345', 'USA', '(800)-555-1212',
NULL );

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:

Violation of PRIMARY KEY constraint 'PK_Customers'.


Cannot insert duplicate key in object 'Customers'.
The statement has been terminated.

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:

UPDATE statement conflicted with COLUMN REFERENCE constraint


'FK_Orders_Customers'. The conflict occurred in database
'Northwind', table 'Orders', column 'CustomerID'.
The statement has been terminated.

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:

DELETE FROM Customers


WHERE CustomerID = 'ALFKI';

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ệ.

1.3.3. Gom nhóm các lệnh SQL


Theo mặc định, khi thực hiện các lệnh INSERT, UPDATE hay DELETE, SQL
Server sẽ ghi nhớ lại kết quả của các lệnh đó trong cơ sở dữ liệu. Điều này không phải
luôn đúng hoặc không như mong muốn.
Chẳng hạn, xét trường hợp giao dịch ngân hàng, bạn muốn rút tiền từ một tài khoản
và chuyển nó vào một tài khoản khác. Nếu có hai lệnh UPDATE riêng biệt được thực
hiện để rút tiền và chuyển tiền, bạn cần phải bảo đảm chúng phải cùng được thực hiện
thành công. Hoặc nếu có một lệnh UPDATE xảy ra lỗi vì một lý do nào đó, bạn muốn
hủy bỏ cả hai lệnh UPDATE để quay lại trạng thái ban đầu.
Việc ghi nhớ kết quả của các lệnh SQL được gọi là “xác nhận” (commit). Việc
hủy bỏ các kết quả đã được tạo ra gọi là “quay lại” (rollback). Ta có thể nhóm các
lệnh SQL thành một đơn vị gọi là giao dịch (transaction). Sau đó, ta có thể xác nhận
hoặc hủy bỏ các lệnh SQL trong giao dịch đó như một lệnh (hay một đơn vị công
việc).
Chẳng hạn, hai lệnh UPDATE trong ví dụ giao dịch ngân hàng có thể được đặt
trong một giao dịch. Sau đó, bạn có thể xác nhận hoặc hủy bỏ giao dịch tùy thuộc vào
việc các lệnh UPDATE có được thực hiện thành công hay không.

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;

INSERT INTO Customers ( CustomerID, CompanyName )


VALUES ( 'SOCOM', 'Steve Orange Company' );

INSERT INTO Orders ( CustomerID )


VALUES ( 'SOCOM' );

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;

INSERT INTO Customers ( CustomerID, CompanyName )


VALUES ( 'SYCOM', 'Steve Yellow Company' );

INSERT INTO Orders ( CustomerID )


VALUES ( 'SYCOM' );

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.

BEGIN TRANSACTION MyTransaction;

INSERT INTO Customers ( CustomerID, CompanyName )


VALUES ( 'SYCOM', 'Steve Yellow Company' );

INSERT INTO Orders ( CustomerID )


VALUES ( 'SYCOM' );

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;

1.3.4. Các lệnh định nghĩa dữ liệu - DDL


Các lệnh DDL – Data Definition Language – cho phép tạo ra các cấu trúc của cơ sở
dữ liệu như bảng, chỉ mục,… Phần này trình bày cách tạo, thay đổi và xóa một bảng
hay chỉ mục.
1.3.4.1. Tạo, thay đổi và xóa một bảng
 Để tạo một bảng, ta dùng lệnh CREATE TABLE.
 Để thay đổi một bảng đang tồn tại, dùng lệnh ALTER TABLE
 Để xóa một bảng, ta dùng lệnh DROP TABLE

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:

CREATE TABLE Persons (


PersonID int CONSTRAINT PK_Persons PRIMARY KEY,
FirstName nvarchar(15) NOT NULL,
LastName nvarchar(15) NOT NULL,
DateOfBirth datetime
);

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.

Ví dụ 2: Thay đổi cấu trúc một bảng


Khi thay đổi cấu trúc một bảng đang tồn tại, bạn có thể thêm hoặc xóa một cột hay
một ràng buộc. Chẳng hạn, để thêm một cột Address vào bảng Persons, cột này có
kiểu chuỗi (nvarchar) có thể lưu trữ tối đa 50 ký tự, ta viết.

Trang 30
Giáo trình lập trình cơ sở dữ liệu

ALTER TABLE Persons


ADD Address nvarchar(50);

Tiếp theo, để xóa cột Address từ bảng Persons, ta viết:

ALTER TABLE Persons


DROP COLUMN Address;

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.

ALTER TABLE Persons


ADD EmployerID nchar(5) CONSTRAINT FK_Persons_Customers REFERENCES
Customers(CustomerID);

Ví dụ 3: Xóa một bảng


DROP TABLE Persons

1.3.4.2. Tạo và xóa chỉ mục (Index)


Lệnh CREATE INDEX được dùng để tạo chỉ mục cho một bảng. Chỉ mục cho
phép bạn tìm một dòng nhanh hơn khi sử dụng các cột có tạo chỉ mục sau mệnh đề
WHERE. Nếu có một cột thường được dùng sau mệnh đề WHERE, nên tạo chỉ mục
cho cột đó.
Chẳng hạn, để tạo chỉ mục cho cột LastName trong bảng Persons, ta viết:
CREATE INDEX LastNameIndex
ON Persons(LastName);

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.

Xóa chỉ mục


Bạn xóa chỉ mục khỏi một bảng bằng cách dùng lệnh DROP INDEX. Chẳng hạn,
lệnh sau sẽ xóa chỉ mục trên cột LastName của bảng Persons.
DROP INDEX Persons.LastNameIndex

Trang 31
Giáo trình lập trình cơ sở dữ liệu

1.3.4.3. Khung nhìn - View


Một khung nhìn (view) có thể được xem như là một bảng “ảo” trong cơ sở dữ liệu
có nội dung được định nghĩa thông qua một truy vấn (câu lệnh SELECT). Như vậy,
một khung nhìn trông giống như một bảng với một tên khung nhìn và là một tập bao
gồm các dòng và các cột. Điểm khác biệt giữa khung nhìn và bảng là khung nhìn
không được xem là một cấu trúc lưu trữ dữ liệu tồn tại trong cơ sở dữ liệu. Thực chất
dữ liệu quan sát được trong khung nhìn được lấy từ các bảng thông qua câu lệnh truy
vấn dữ liệu.
Việc sử dụng khung nhìn trong cơ sở dữ liệu đem lại các lợi ích sau đây:
 Bảo mật dữ liệu: Người sử dụng được cấp phát quyền trên các khung nhìn với
những phần dữ liệu mà người sử dụng được phép. Điều này hạn chế được phần
nào việc người sử dụng truy cập trực tiếp dữ liệu.
 Đơn giản hoá các thao tác truy vấn dữ liệu: Một khung nhìn đóng vai trò như là
một đối tượng tập hợp dữ liệu từ nhiều bảng khác nhau vào trong một “bảng”.
Nhờ vào đó, người sử dụng có thể thực hiện các yêu cầu truy vấn dữ liệu một
cách đơn giản từ khung nhìn thay vì phải đưa ra những câu truy vấn phức tạp.
 Tập trung và đơn giản hóa dữ liệu: Thông qua khung nhìn ta có thể cung cấp
cho người sử dụng những cấu trúc đơn giản, dễ hiểu hơn về dữ liệu trong cơ sở
dữ liệu đồng thời giúp cho người sử dụng tập trung hơn trên những phần dữ liệu
cần thiết.
 Độc lập dữ liệu: Một khung nhìn có thể cho phép người sử dụng có được cái
nhìn về dữ liệu độc lập với cấu trúc của các bảng trong cơ sở dữ liệu cho dù các
bảng cơ sở có bị thay đổi phần nào về cấu trúc.

Câu lệnh CREATE VIEW được sử dụng để tạo ra khung nhìn và có cú pháp như sau:

CREATE VIEW tên_khung_nhìn[(danh_sách_tên_cột)]


AS
câu_lệnh_SELECT

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.

CREATE VIEW ListProducts


AS
SELECT ProductID, ProductName, UnitsInStock
CategoryName, CompanyName AS Supplier
FROM Products pro, Categories cat, Suppliers sup
WHERE pro.CategoryID = cat.CategoryID
AND pro.SupplierID = sup.SupplierID

1.3.5. Thủ tục, hàm và trigger


1.3.5.1. Thủ tục lưu trữ (Stored Procedure)
Các câu lệnh SQL có thể được nhúng vào trong các ngôn ngữ lập trình, thông qua
đó chuỗi các thao tác trên cơ sở dữ liệu được xác định và thực thi nhờ vào các câu
lệnh, các cấu trúc điều khiển của bản thân ngôn ngữ lập trình được sử dụng.
Với thủ tục lưu trữ, một phần nào đó khả năng của ngôn ngữ lập trình được đưa
vào trong ngôn ngữ SQL. Một thủ tục là một đối tượng trong cơ sở dữ liệu bao gồm
một tập nhiều câu lệnh SQL được nhóm lại với nhau thành một nhóm với những khả
năng sau:
 Các cấu trúc điều khiển (IF, WHILE, FOR) có thể được sử dụng trong thủ tục.
 Bên trong thủ tục lưu trữ có thể sử dụng các biến như trong ngôn ngữ lập trình
nhằm lưu giữ các giá trị tính toán được, các giá trị được truy xuất được từ cơ sở
dữ liệu.
 Một tập các câu lệnh SQL được kết hợp lại với nhau thành một khối lệnh bên
trong một thủ tục. Một thủ tục có thể nhận các tham số truyền vào cũng như có
thể trả về các giá trị thông qua các tham số (như trong các ngôn ngữ lập trình).
Khi một thủ tục lưu trữ đã được định nghĩa, nó có thể được gọi thông qua tên
thủ tục, nhận các tham số truyền vào, thực thi các câu lệnh SQL bên trong thủ
tục và có thể trả về các giá trị sau khi thực hiện xong.
Sử dụng các thủ tục lưu trữ trong cơ sở dữ liệu sẽ giúp tăng hiệu năng của cơ sở dữ
liệu, mang lại các lợi ích sau:
 Đơn giản hoá các thao tác trên cơ sở dữ liệu nhờ vào khả năng module hoá các
thao tác này.
 Thủ tục lưu trữ được phân tích, tối ưu khi tạo ra nên việc thực thi chúng nhanh
hơn nhiều so với việc phải thực hiện một tập rời rạc các câu lệnh SQL tương
đương theo cách thông thường.

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.

CREATE PROCEDURE AddProduct


@MyProductName nvarchar(40),
@MySupplierID int,
@MyCategoryID int,
@MyQuantityPerUnit nvarchar(20),
@MyUnitPrice money,
@MyUnitsInStock smallint,
@MyUnitsOnOrder smallint,
@MyReorderLevel smallint,
@MyDiscontinued bit
AS
DECLARE @ProductID int

-- insert a row into the Products table

Trang 34
Giáo trình lập trình cơ sở dữ liệu

INSERT INTO Products (


ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel,
Discontinued
) VALUES (
@MyProductName, @MySupplierID, @MyCategoryID, @MyQuantityPerUnit,
@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, @MyReorderLevel,
@MyDiscontinued
)

-- use the @@IDENTITY function to get the last inserted


-- identity value, which in this case is the ProductID of
-- the new row in the Products table
SET @ProductID = @@IDENTITY

-- return the ProductID


RETURN @ProductID

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ố ]

1.3.5.2. Hàm - Function


Hàm là đối tượng cơ sở dữ liệu tương tự như thủ tục. Điểm khác biệt giữa hàm và
thủ tục là hàm trả về một giá trị thông qua tên hàm còn thủ tục thì không. Điều này cho
phép ta sử dụng hàm như là một thành phần của một biểu thức (chẳng hạn trong danh
sách chọn của câu lệnh SELECT).
Ngoài những hàm do hệ quản trị cơ sở dữ liệu cung cấp sẵn, người sử dụng có thể
định nghĩa thêm các hàm nhằm phục vụ cho mục đích riêng của mình.
Hàm được định nghĩa thông qua câu lệnh CREATE FUNCTION với cú pháp như sau:
CREATE FUNCTION tên_hàm ( [danh_sách_tham_số] )
RETURNS ( kiểu_trả_về_của_hàm )
AS
BEGIN
các_câu_lệnh_của_hàm
END

Ví dụ: Tạo hàm tính tiền giảm giá cho một sản phẩm

CREATE FUNCTION DiscountPrice(@OriginalPrice money, @Discount float)


RETURNS money
AS
BEGIN

Trang 35
Giáo trình lập trình cơ sở dữ liệu

RETURN @OriginalPrice * @Discount


END

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ính tiền giảm với Discount = 0.3 và UnitPrice


-- lấy từ bảng Products của csdl Northwind
SELECT dbo.DiscountPrice(UnitPrice, 0.3), UnitPrice
FROM Products
WHERE ProductID = 1;

-- Tương tự
DECLARE @MyDiscountFactor float
SET @MyDiscountFactor = 0.3
SELECT dbo.DiscountPrice(UnitPrice, @MyDiscountFactor), UnitPrice
FROM Products
WHERE ProductID = 1;

Hàm trả về dữ liệu kiểu bảng (Table)


Ta đã biết được chức năng cũng như sự tiện lợi của việc sử dụng các khung nhìn
trong cơ sở dữ liệu. Tuy nhiên, nếu cần phải sử dụng các tham số trong khung nhìn
(chẳng hạn các tham số trong mệnh đề WHERE của câu lệnh SELECT) thì ta lại
không thể thực hiện được. Điều này phần nào đó làm giảm tính linh hoạt trong việc sử
dụng khung nhìn.
Nhược điểm trên của khung nhìn có thể khắc phục bằng cách sử dụng hàm với giá
trị trả về dưới dạng bảng và được gọi là hàm nội tuyến (inline function). Việc sử dụng
hàm loại này cung cấp khả năng như khung nhìn nhưng cho phép chúng ta sử dụng
được các tham số và nhờ đó tính linh hoạt sẽ cao hơn.
Một hàm nội tuyến được định nghĩa bởi câu lệnh CREATE TABLE với cú pháp
như sau:
CREATE FUNCTION tên_hàm ( [danh_sách_tham_số] )
RETURNS TABLE
AS
RETURN ( câu_lệnh_select )

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.

CREATE FUNCTION ProductsToBeReordered(@ReorderLevel int)


RETURNS table
AS
RETURN (
SELECT *
FROM Products
WHERE UnitsInStock <= @ReorderLevel )

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);

-- hoặc lấy các cột cụ thể


SELECT ProductID, ProductName, UnitsInStock
FROM ProductsToBeReordered(10)
WHERE ProductID <= 50;

Đố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:

CREATE FUNCTION tên_hàm ( [danh_sách_tham_số] )


RETURNS @biến_bảng TABLE định_nghĩa_bảng
AS
BEGIN
các_câu_lệnh_trong_thân_hàm
RETURN
END

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.

CREATE FUNCTION ProductsToBeReordered2(@ReorderLevel int)


RETURNS @MyProducts table
(

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

-- retrieve rows from the Products table and


-- insert them into the MyProducts table,
-- setting the Reorder column to 'No'
INSERT INTO @MyProducts
SELECT ProductID, ProductName, UnitsInStock, 'No'
FROM Products;

-- update the MyProducts table, setting the


-- Reorder column to 'Yes' when the UnitsInStock
-- column is less than or equal to @ReorderLevel
UPDATE @MyProducts
SET Reorder = 'Yes'
WHERE UnitsInStock <= @ReorderLevel

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:

SELECT * FROM ProductsToBeReordered2(20);

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

Ví dụ: Ta định nghĩa các bảng như sau:


Bảng MATHANG lưu trữ dữ liệu về các mặt hàng:

CREATE TABLE mathang


(
mahang NVARCHAR(5) PRIMARY KEY, /*mã hàng*/
tenhang NVARCHAR(50) NOT NULL, /*tên hàng*/
soluong INT, /*số lượng hàng hiện có*/
)

Bảng NHATKYBANHANG lưu trữ thông tin về các lần bán hàng

CREATE TABLE nhatkybanhang


(
stt INT IDENTITY PRIMARY KEY,
ngay DATETIME, /*ngày bán hàng*/
nguoimua NVARCHAR(30), /*tên người mua hàng*/
mahang NVARCHAR(5) /*mã mặt hàng được bán*/
FOREIGN KEY REFERENCES mathang(mahang),
soluong INT, /*giá bán hàng*/
giaban MONEY /*số lượng hàng được bán*/
)

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).

CREATE TRIGGER trg_nhatkybanhang_insert


ON nhatkybanhang
FOR INSERT
AS
UPDATE mathang
SET mathang.soluong = mathang.soluong - inserted.soluong
FROM mathang INNER JOIN inserted
ON mathang.mahang=inserted.mahang

Với trigger vừa tạo ở trên, nếu dữ liệu trong bảng MATHANG là:

Mã Hàng Tên Hàng Số Lượng


H1 Xà phòng 30
H2 Kem đánh răng 45

thì sau khi ta thực hiện câu lệnh:

INSERT INTO nhatkybanhang


(ngay,nguoimua,mahang,soluong,giaban)
VALUES('5/5/2004','Tran Ngoc Thanh','H1',10,5200)

Dữ liệu trong bảng MATHANG sẽ như sau:

Mã Hàng Tên Hàng Số Lượng


H1 Xà phòng 20
H2 Kem đánh răng 45

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)

CREATE TRIGGER trg_nhatkybanhang_update_soluong


ON nhatkybanhang
FOR UPDATE
AS
IF UPDATE(soluong)
UPDATE mathang
SET mathang.soluong = mathang.soluong –
(inserted.soluong-deleted.soluong)
FROM (deleted INNER JOIN inserted ON
deleted.stt = inserted.stt) INNER JOIN mathang
ON mathang.mahang = deleted.mahang

Với trigger ở ví dụ trên, câu lệnh:

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.

1.4. Giao dịch SQL – Transaction SQL


Một giao dịch (transaction) là một chuỗi một hoặc nhiều câu lệnh SQL được kết
hợp lại với nhau thành một khối công việc. Các câu lệnh SQL xuất hiện trong giao
dịch thường có mối quan hệ tương đối mật thiết với nhau và thực hiện các thao tác độc
lập. Việc kết hợp các câu lệnh lại với nhau trong một giao dịch nhằm đảm bảo tính
toàn vẹn dữ liệu và khả năng phục hồi dữ liệu. Trong một giao dịch, các câu lệnh có
thể độc lập với nhau nhưng tất cả các câu lệnh trong một giao dịch đòi hỏi hoặc phải
thực thi trọn vẹn hoặc không một câu lệnh nào được thực thi.
Các cơ sở dữ liệu sử dụng nhật ký giao dịch (transaction log) để ghi lại các thay
đổi mà giao dịch tạo ra trên cơ sở dữ liệu và thông qua đó có thể phục hồi dữ liệu trong
trường hợp gặp lỗi hay hệ thống có sự cố.

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.

1.4.1. Mô hình giao dịch trong SQL


Giao dịch SQL được định nghĩa dựa trên các câu lệnh xử lý giao dịch sau đây:
 BEGIN TRANSACTION: Bắt đầu một giao dịch
 SAVE TRANSACTION: Đánh dấu một vị trí trong giao dịch (gọi là điểm đánh
dấu).
 ROLLBACK TRANSACTION: Quay lui trở lại đầu giao dịch hoặc một điểm
đánh dấu trước đó trong giao dịch.
 COMMIT TRANSACTION: Đánh dấu điểm kết thúc một giao dịch. Khi câu
lệnh này thực thi cũng có nghĩa là giao dịch đã thực hiện thành công.
 ROLLBACK [WORK]: Quay lui trở lại đầu giao dịch.
 COMMIT [WORK]: Đánh dấu kết thúc giao dịch.
Một giao dịch trong SQL được bắt đấu bởi câu lệnh BEGIN TRANSACTION. Câu
lệnh này đánh dấu điểm bắt đầu của một giao dịch và có cú pháp như sau:

BEGIN TRANSACTION [tên_giao_dịch]

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.

Hình 1.10. Mô hình giao dịch trong SQL

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.

BEGIN TRANSACTION giaotac1


UPDATE Customers SET Country='US' WHERE Country='USA'
UPDATE Products SET CategoryID=1 WHERE CategoryID IS NULL
ROLLBACK TRANSACTION giaotac1

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.

BEGIN TRANSACTION giaotac2


UPDATE Customers SET Country='US' WHERE Country='USA'
UPDATE Products SET CategoryID=1 WHERE CategoryID IS NULL
COMMIT TRANSACTION giaotac2

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:

ROLLBACK TRANSACTION tên_điểm_đánh_dấu

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

BEGIN TRANSACTION giaotac3


UPDATE Products SET CategoryID=1 WHERE CategoryID IS NULL
SAVE TRANSACTION a
UPDATE Customers SET Country='US' WHERE Country='USA'
ROLLBACK TRANSACTION a
UPDATE Customers SET Country='USE' WHERE Country='USA'
COMMIT TRANSACTION giaotac3

và trong ví dụ dưới đây, câu lệnh COMMIT TRANSACTION gặp lỗi:

BEGIN TRANSACTION giaotac4


UPDATE Products SET CategoryID=1 WHERE CategoryID IS NULL
SAVE TRANSACTION a
UPDATE Customers SET Country='US' WHERE Country='USA'
ROLLBACK TRANSACTION giaotac4
UPDATE Customers SET Country='USE' WHERE Country='USA'
COMMIT TRANSACTION giaotac4

1.4.2. Giao dịch lồng nhau


Các giao dịch trong SQL có thể được lồng vào nhau theo từng cấp. Điều này
thường gặp đối với các giao dịch trong các thủ tục lưu trữ được gọi hoặc từ một tiến
trình trong một giao dịch khác.

Trang 44
Giáo trình lập trình cơ sở dữ liệu

Hình 1.11. Hoạt động của một giao dịch

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
)

và thủ tục sp_TransEx:

CREATE PROC sp_TranEx(@a INT,@b INT)


AS
BEGIN
BEGIN TRANSACTION T1
IF NOT EXISTS (SELECT * FROM T WHERE A=@A )

Trang 45
Giáo trình lập trình cơ sở dữ liệu

INSERT INTO T VALUES(@A,@B)


IF NOT EXISTS (SELECT * FROM T WHERE A=@A+1)
INSERT INTO T VALUES(@A+1,@B+1)
COMMIT TRANSACTION T1
END

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.

1.5. 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.

1.5.1. Bảng Customers


Bảng Customers chứa các hàng lưu trữ thông tin chi tiết về các công ty có đặt hàng
cho Northwind. Hình 1.22 cho thấy một vài dòng trong bảng Customers.

Hình 1.22. Một vài mẫu tin trong bảng Customers


Dòng đầu tiên trong bảng cho biết có một khách hàng tên Alfreds Futterkiste. Tên
này được lưu trong cột CompanyName của bảng Customers. Giá trị tại cột
CustomerID của dòng này là ALFKI, giá trị này là duy nhất trên toàn bộ các dòng
trong bảng. Như đã nói ở phần trước, CustomerID là khóa chính của bảng Customers
nên nếu bạn thêm một dòng mới với giá trị khóa chính trùng với các giá trị đã tồn tại,
cơ sở dữ liệu sẽ tự động loại bỏ dòng mới.
Định nghĩa bảng Customers
Bảng sau cho thấy định nghĩa của bảng Customers trong cơ sở dữ liệu Northwind,
nó liệt kê tên cột, kiểu dữ liệu, chiều dài và xác định cột nào được phép chứa giá trị
null.
COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?
CustomerID nchar 5 No
CompanyName nvarchar 40 No
ContactName nvarchar 30 Yes
ContactTitle nvarchar 30 Yes
Address nvarchar 60 Yes

Trang 47
Giáo trình lập trình cơ sở dữ liệu

COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?


City nvarchar 15 Yes
Region nvarchar 15 Yes
PostalCode nvarchar 10 Yes
Country nvarchar 15 Yes
Phone nvarchar 24 Yes
Fax nvarchar 24 Yes
Bảng 1.4. Định nghĩa các cột của bảng Customers

1.5.2. Bảng Orders


Bảng Orders chứa các hàng lưu trữ thông tin đặt hàng của khách hàng. Hình sau
cho thấy một vài mẫu tin trong bảng Orders.

Hình 1.23. Một vài mẫu tin trong bảng Orders


Khóa chính của bảng Orders là cột OrderID. Điều này cũng có nghĩa là giá trị trong
cột này phải là duy nhất trên mọi dòng và khác null. Nếu để ý kỹ 6 dòng đầu của bảng
Orders, bạn sẽ thấy cột CustomerID có cùng một giá trị ALFKI. Giá trị này giống với
giá trị của cột CustomerID trong dòng đầu tiên của bảng Customers (hình 1.22). Bây
giờ, bạn có thể thấy khóa ngoại liên kết các thông tin như thế nào. Cột CustomerID
trong bảng Orders là khóa ngoại tham chiếu đến cột CustomerID trong bảng
Customers. Vì thế, trong trường hợp này, có thể xem khóa ngoại như một con trỏ từ
bảng Orders sang bảng Customers. Bảng sau cho thấy định nghĩa các cột của Orders.
COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?
OrderID int 4 No
CustomerID nchar 5 Yes
EmployeeID int 4 Yes
OrderDate datetime 8 Yes
RequiredDate datetime 8 Yes
ShippedDate datetime 8 Yes
ShipVia int 4 Yes
Freight money 8 Yes

Trang 48
Giáo trình lập trình cơ sở dữ liệu

COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?


ShipName nvarchar 40 Yes
ShipAddress nvarchar 60 Yes
ShipCity nvarchar 15 Yes
ShipRegion nvarchar 15 Yes
ShipPostalCode nvarchar 10 Yes
ShipCountry nvarchar 15 Yes
Bảng 1.5. Định nghĩa các cột của bảng Orders.

1.5.3. Bảng Order Details


Bảng này chứa các dòng lưu trữ thông tin chi tiết của mỗi đơn đặt hàng. Hình sau
minh họa chi tiết của một đơn đặt hàng có mã (OrderID) là 10643.

Hình 1.24. Chi tiết đơn đặt hàng có mã 10643


Khóa chính của bảng Order Details gồm hai cột OrderID và ProductID, có nghĩa là
cần phải kết hợp giá trị giữa hai cột này mới tạo ra được tính duy nhất cho mỗi dòng.
Ngoài ra, cột OrderID của bảng Order Details là khóa ngoại tham chiếu đến cột
OrderID của bảng Orders và cột ProductID của bảng Order Details là khóa ngoại tham
chiếu đến bảng Products.
Bảng sau cho thấy định nghĩa các cột của bảng Order Details.
COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?
OrderID Int 4 Yes
ProductID Int 4 Yes
UnitPrice money 8 Yes
Quantity smallint 2 Yes
Discount real 4 Yes
Bảng 1.6. Định nghĩa các cột của bảng Order Details

1.5.4. Bảng Products


Bảng này chứa các hàng lưu trữ thông tin chi tiết của mỗi sản phẩm được bán bởi
công ty Northwind. Hình sau minh họa một vài mẫu tin trong bảng Products. Những
mẫu tin này được rút ra theo ProductID trong bảng Order Details ở hình 1.25 gồm các
sản phẩm có mã 28, 39, 46.

Trang 49
Giáo trình lập trình cơ sở dữ liệu

Hình 1.25. Một vài mẫu tin trong bảng Products.


Khóa chính của bảng Products là cột ProductID. Cột CategoryID của bảng
Products là khóa ngoại tham chiếu đến cột CategoryID của bảng Categories. Bảng này
chứa các loại sản phẩm được bán bởi Northwind.
Cột SupplierID của bảng Products là một khóa ngoại tham chiếu đến cột
SupplierID của bảng Suppliers. Bảng này chứa thông tin về các nhà cung cấp sản
phẩm cho công ty Northwind.
Bảng sau cho biết định nghĩa các cột của bảng Products trong cơ sở dữ liệu
Northwind.
COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?
ProductID int 4 No
ProductName nvarchar 40 No
SupplierID int 4 Yes
CategoryID int 4 Yes
QuantityPerUnit nvarchar 20 Yes
UnitPrice money 8 Yes
UnitsInStock smallint 2 Yes
UnitsOnOrder smallint 2 Yes
ReorderLevel smallint 2 Yes
Discontinued bit 1 Yes
Bảng 1.7. Định nghĩa các cột của bảng Products

1.6. Sử dụng Microsoft SQL Server


Để khởi động và kết thúc SQL Server, ta sử dụng công cụ Service Manager (Hình
1.12). Để mở Service Manager, chọn Start  Programs  Microsoft SQL Server 
Service Manager.

Trang 50
Giáo trình lập trình cơ sở dữ liệu

Hình 1.12. SQL Server Service Manager


Chọn tên của máy chủ đang chạy SQL Server được liệt kê trong mục Server. Để khởi
động SQL Server, bạn nhấp vào nút Start/Continue. Để kết thúc SQL Server, nhấp vào
nút Stop. Ta cũng có thể tạm ngưng SQL Server hoặc cấu hình cho phép nó tự động
chạy lúc hệ điều hành khởi động.
Một khi SQL Server đã được khởi động, các chương trình khác có thể truy xuất đến
các cơ sở dữ liệu được quản lý bởi trình cài đặt SQL Server.
1.6.1. Sử dụng Enterprise Manager
Để quản lý một cơ sở dữ liệu, ta dùng công cụ Enterprise Manager (hình 1.13). Bạn
có thể tạo các cơ sở dữ liệu, tạo và hiệu chỉnh các bảng, người dùng, … bằng
Enterprise Manager. Để mở chương trình Enterprise Manager, chọn Start  Programs
 Microsoft SQL Server  Enterprise Manager.

Hình 1.13. SQL Server Enterprise Manager

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:

Hình 1.14. Lược đồ quan hệ giữa các bảng.


 Diagrams: là một lược đồ để lưu trữ và biểu diễn một cách trực quan các bảng
trong cơ sở dữ liệu. Chẳng hạn, cơ sở dữ liệu Northwind có chứa 4 bảng sau:
Customers, Orders, Order Details và Products. Hình 1.14 minh họa mối quan hệ
giữa các bảng này. Các cột của bảng được liệt kê ở các ô bên dưới tên bảng
trong lược đồ. Chẳng hạn, bảng Customers có chứa tất cả 11 cột: CustomerID,
CompanyName, ContactName, ContactTitle, Address, City, Region,
PostalCode, Country, Phone và Fax. Đường kẻ có hình chìa khóa nối các bảng
chỉ ra mối quan hệ giữa các bảng.
 Tables: Bạn sử dụng bảng để lưu trữ các dòng dữ liệu. Mỗi dòng lại được chia
làm nhiều cột. Hình 1.15 liệt kê một số bảng được lưu trữ trong cơ sở dữ liệu
Northwind.
 Views: Bạn sử dụng khung nhìn (views) để truy vấn đến một tập các cột từ một
hay nhiều bảng khác nhau. Có thể xem khung nhìn là một cách linh hoạt hơn
nhiều trong việc xử lý dữ liệu được lưu trữ trên nhiều bảng. Chẳng hạn, một
trong những khung nhìn có trong cơ sở dữ liệu Northwind là Alphabetical list of
products dùng để truy vấn danh sách các sản phẩm cùng với tên nhóm sản
phẩm. Thông tin này được lấy từ cả hai bảng Products và Categories. Bạn có
thể tạo một khung nhìn mới, khảo sát các thuộc tính của khung nhìn hay truy
vấn dữ liệu thông qua view. Để xem các thuộc tính của view, nhắp phải chuột
và chọn Properties hoặc nhấp đôi chuột vào view cần xem thuộc tính. Hình 1.17
cho thấy các thuộc tính của của khung nhìn Alphabetical list of products. Nội

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.

Hình 1.15. Các bảng trong cơ sở dữ liệu Northwind


 Stored Procedures: được dùng để chạy một dãy các lệnh trong cơ sở dữ liệu.
Trong SQL Server, các thủ tục (stored procedure) được viết dưới dạng
Transact-SQL. Các thủ tục được lưu trong cơ sở dữ liệu và thường được sử
dụng khi cần thực hiện một thao tác phức tạp hoặc khi bạn muốn kiểm soát một
chức năng trong cơ sở dữ liệu mà bất kỳ người dùng nào cũng có thể gọi nó
thay vì phải viết lại trong chương trình của họ để thực hiện cùng một công việc.
Chẳng hạn, trong cơ sở dữ liệu Northwind có một thủ tục CustOrdHist, thủ tục
này trả về tên sản phẩm và tổng số lượng các sản phẩm được đặt hàng bởi một
khách hàng nào đó. Mã khách hàng được gửi vào thủ tục thông qua một tham
số. Hình 1.18 cho thấy nội dung và thuộc tính của thủ tục CustOrdHist.

Trang 54
Giáo trình lập trình cơ sở dữ liệu

Hình 1.16. Các thuộc tính của bảng Customers

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

Hình 1.18. Nội dung thủ tục CustOrdHist

Hình 1.19. Các thuộc tính của tài khoản dbo

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.

Hình 1.20. Các thuộc tính của public role

Trang 57
Giáo trình lập trình cơ sở dữ liệu

Hình 1.21. Các quyền của public role


Bảng sau mô tả ý nghĩa của các quyền được gán cho role
PERMISSION MEANING
SELECT Cho phép truy vấn các mẫu tin từ một bảng hay khung nhìn.
INSERT Cho phép thêm một mẫu tin vào một bảng hay khung nhìn.
UPDATE Cho phép cập nhật dữ liệu các dòng trong bảng hay khung
nhìn.
DELETE Cho phép xóa các mẫu tin khỏi bảng hoặc khung nhìn.
EXEC Cho phép chạy một thủ tục (execution of a stored
procedure).
DRI Cho phép thêm hoặc xóa các ràng buộc toàn vẹn có liên quan
(DRI – Declarative referential integrity) một bảng. Các
ràng buộc này nhằm bảo đảm rằng các thao tác được thực hiện
thích hợp khi thêm, cập nhật hay xóa các giá trị khóa
ngoại. Các khóa ngoại chỉ ra rằng một cột trong bảng này có
quan hệ đến một cột trong một bảng khác.
Bảng 1.3. Ý nghĩa của các quyền được xây dựng sẵn
 Rules: Luật hay quy tắc (rule) là một biểu thức trả về giá trị đúng (true) hoặc
sai (false) và xác định xem bạn có thể gán một giá trị vào một cột nào đó hay
không. Chẳng hạn, bạn có thể định nghĩa một rule để chỉ ra một miền giá trị và
nếu giá trị nhập vượt khỏi miền này thì bạn không thể gán giá trị đó vào cột
trong bảng. Rules cung cấp tính năng để tương thích với các phiên bản cũ hơn
của SQL Server. Nay nó đã được thay bằng thuật ngữ mới: ràng buộc
(constraint).

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.

1.6.1.2. Tạo bảng dữ liệu


Bạn có thể dùng Enterprise Manager để thêm các bảng mới vào một cơ sở dữ liệu.
Trong phần này, ta sẽ thêm một bảng vào cơ sở dữ liệu Northwind để lưu trữ thông tin
chi tiết của một người. Bảng này có tên là Persons và chứa các cột được định nghĩa
như sau:
COLUMN NAME DATABASE TYPE LENGTH ALLOWS NULL VALUES?
PersonID int 4 No
FirstName nvarchar 15 No
LastName nvarchar 15 No
DateOfBirth datetime 8 Yes
Address nvarchar 50 Yes
EmployerID nchar 5 No
Bảng 1.8. Định nghĩa các cột của bảng Persons
Để tạo một bảng trong cơ sở dữ liệu Northwind, chọn mục Tables của thư mục
Northwind trong Enterprise Manager rồi chọn Action  New Table, xuất hiện màn
hình thiết kế. Thêm các cột trong bảng 1.8 vào khung thiết kế như trong hình 1.28.

Hình 1.28. Thêm một bảng mới Persons.


Lưu ý: Kích thước của một vài kiểu dữ liệu là không đổi. Chẳng hạn, kiểu int luôn
chiếm 4 bytes trong bộ nhớ. Vì thế, không thể thay đổi kích thước của một cột kiểu int.
tương tự, kiểu datetime luôn chiếm 8 bytes trong bộ nhớ. Bạn chỉ có thể thay đối kích

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.

Hình 1.29. Đặt tên cho bảng


Tiếp theo ta sẽ thêm các thông tin bổ sung cho các cột của bảng thông qua thẻ
Columns, đặt khóa chính, thiết lập quyền truy xuất đến nội dung của bảng, tạo quan hệ
giữa các bảng, tạo chỉ mục để tăng tốc độ truy xuất dữ liệu của bảng, tạo các ràng buộc
về giá trị cho các cột của bảng.

1.6.1.2.1. Thẻ Columns


Ngay bên dưới lưới để thiết kế các cột của bảng, có một thẻ tên là Columns. Thẻ
này chứa các thông tin hay các thuộc tính bổ sung cho cột hiện đang được chọn trên
lưới. hình 1.20 trên đây chỉ ra các thông tin trên cột PersonID. Khi cột được chọn thay
đổi, thông tin trong thẻ Columns cũng thay đổi theo.
Bạn có thể nhập một đoạn mô tả tùy ý cho một cột trong trường Description.
Trường Default Value cho phép bạn cung cấp một giá trị mặc định, được dùng khi
một dòng mới được thêm vào bảng. Tuy nhiên, bạn vẫn có thể đưa giá trị của mình
vào cột đó và giá trị này sẽ ghi đè lên giá trị mặc định.
Trường Precision cho biết số chữ số tối đa được phép dùng để lưu trữ một số, bao
gồm cả các chữ số nằm sau dấu chấm thập phân. Trường Scale cho biết số chữ tối đa
được phép dùng sau dấu chấm thập phân. Chẳng hạn, precision và scale của một cột có
kiểu int là 10 và 0, có nghĩa là một cột kiểu int có thể chứa tối đa 10 chữ số và không
có chữ số nằm bên phải dấu chấm thập phân (int là kiểu số nguyên). Precision và scale
của kiểu một cột có kiểu tiền tệ (money) là 19 và 4, nghĩa là một cột kiểu money có
thể chứa tới 19 ký số và tối đa 4 chữ số thập phân nằm sau dấu chấm.
Trường Identity cho phép bạn yêu cầu SQL Server tự động gán giá trị cho một cột
nào đó. Nếu giá trị trong trường này là Yes, bạn có thể chỉ ra giá trị cho các trường
Identity Seed và Identity Increament. Trường Identity Seed dùng để khởi tạo giá trị
bắt đầu cho cột. Trường Identity Increament chỉ ra giá trị tăng dần sau mỗi lần tự động
gán. Theo mặc định, hai giá trị này đều được đặt là 1. Nghĩa là là giá trị đầu tiên trong
cột đó là 1, giá trị tiếp theo là 2,… Cột ProductID của bảng Products là một ví dụ điển
hình về việc sử dụng định danh (identity) để gán giá trị.

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ữ.

1.6.1.2.2. Đặt khóa chính cho bảng


Để đặt khóa chính cho bảng Persons, nhấp chọn dòng đầu tiên (dòng chứa cột
PersonID), nhấp chọn nút Set Primary Key trên thanh công cụ. Một biểu tượng hình
chìa khóa sẽ hiện lên bên trái dòng PersonID nếu thành công.

1.6.1.3. Thiết lập quyền


Để thiết lập quyền trên một bảng, nhấp chọn nút Show Permissions trên thanh
công cụ của cửa sổ Table Designer. Gán các quyền SELECT, INSERT, UPDATE và
DELETE cho public role như hình 1.30. Những quyền này cho phép người dùng bất
kỳ truy vấn, thêm, cập nhật hay xóa các dòng dữ liệu khỏi bảng Persons.

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

1.6.1.4. Tạo quan hệ giữa các bảng


Để xem màn hình thể hiện mối quan hệ giữa các bảng, nhấp chọn nút Manage
Relationships trên thanh công cụ của cửa sổ Table Designer. Nhấp chọn New để tạo
quan hệ. Chọn bảng Customers trong mục Primary key table rồi chọn cột CustomerID
trong bảng này. Trong mục Foreign key table, chọn bảng Persons và chọn cột
EmployeeID như trong hình 1.31. Chú ý rằng, tên quan hệ được SQL Server đặt tự
động.

Hình 1.31. Tạo quan hệ


Ý nghĩa của các dòng bên dưới hộp thoại có ý nghĩa như sau:
- Check existing data on creation: Áp dụng các ràng buộc đối với dữ liệu đã tồn
tại trong cơ sở dữ liệu khi bạn thêm quan hệ tới bảng chứa khóa ngoại.
- Enforce relationship for replication: Sự tái tạo (replication) cho phép bạn sao
chép thông tin đến một cơ sở dữ liệu khác. Khi tùy chọn này được chọn, ràng
buộc của bạn được áp dụng cho bảng chứa khóa ngoại cũng được sao chép sang
cơ sở dữ liệu khác.
- Enforce relationship for INSERTs and UPDATEs: áp dụng các ràng buộc lên
các dòng dữ liệu được thêm, cập nhật hay xóa khỏi bảng chứa khóa ngoại. Nó

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.

1.6.1.5. Tạo chỉ mục


Chỉ mục cho phép cơ sở dữ liệu định vị một hàng nhanh chóng khi có yêu cầu truy
xuất dữ liệu theo giá trị cụ thể của một cột nào đó. Trong phần này, ta sẽ tạo chỉ mục
cho cột LastName của bảng Persons.
Để xem các chỉ mục của bảng Persons, nhấp nút Manage Indexes/Keys trên thanh
công cụ của cửa sổ Table Designer. Nhấn nút New để tạo một chỉ mục mới. Đặt tên
cho chỉ mục này là IX_LastName_Persons. Chọn cột LastName ở khung bên dưới và
đặt giá trị Order là Ascending như hình 1.32.

Hình 1.32. Tạo chỉ mục

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

1.6.1.6. Tạo một ràng buộc (constraint)


Một ràng buộc cho phép bạn định nghĩa giới hạn của các giá trị được lưu trong một
cột. Chẳng hạn, bạn muốn tạo ràng buộc trên cột DateOfBirth của bảng Persons. Ràng
buộc này sẽ bảo đảm rằng bạn chỉ có thể gán các giá trị ngày từ January 1, 1950 đến
December 31, 2050 cho cột DateOfBirth.
Để xem các ràng buộc của bảng Persons, nhấp chọn nút Manage Constraints trên
thanh công cụ của cửa sổ Table Designer. Nhấn nút New để tạo một ràng buộc mới.
Nhập biểu thức ràng buộc “([DateOfBirth] >= '1/1/1950' and [DateOfBirth] <=
'12/31/2050')” vào ô Constraint Expression và đặt tên cho ràng buộc là
CK_DateOfBirth_Persons như hình 1.33.

Hình 1.33. Tạo một ràng buộc


Bạn không cần phải chọn các tùy chọn trong hộp thoại trên. Tuy nhiên, vẫn cần
phải biết ý nghĩa của mỗi tùy chọn là gì.
- Check existing data on creation: sử dụng tùy chọn này để bảo đảm rằng dữ liệu
đang tồn tại trong bảng cũng phải thỏa mãn ràng buộc.

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.

1.6.2. Query Analyzer


Trình Query Analyzer được dùng để tạo và thực thi các lệnh SQL. Để mở chương
trình này, chọn menu Start ➣ Microsoft SQL Server ➣ Query Analyzer.

1.6.2.1. Kết nối tới SQL Server


Khi khởi động Query Analyzer, một hộp thoại Connect to SQL Server xuất hiện
yêu cầu nhập tên của SQL Server mà bạn muốn kết nối. Bạn có thể chọn tên này trong
danh sách sổ xuống hoặc nhấp vào nút có dấu “…” để hiển thị danh sách các máy SQL
Server đang chạy trong mạng.

Hình 1.34. Kết nối tới SQL Server từ Query Analyzer.


Nếu chọn Windows Authentication, SQL Server sẽ dùng thông tin người dùng của
hệ điều hành để xác nhận yêu cầu kết nối tới SQL Server. Nếu chọn SQL Server
authentication thì bạn phải nhập tên đăng nhập và mật khẩu.
Trong hình trên, tên SQL Server được chọn là localhost (local) tương ứng với SQL
Server được cài đặt trên máy cục bộ. Kiểu chứng thực được chọn là SQL Server
authentication với tên đăng nhập là sa và mật khẩu là sa. Đây là mật khẩu được thiết
lập khi cài đặt SQL Server.

Trang 69
Giáo trình lập trình cơ sở dữ liệu

1.6.2.2. Tạo và thực thi một lệnh truy vấn SQL


Một khi đã kết nối được với SQL Server bằng Query Analyzer, bạn có thể dùng
Object Browser để xem các thành phần của một cơ sở dữ liệu, tạo và thực thi các lệnh
truy vấn SQL trong cửa sổ Query. Hình 1.35 cho thấy Object Browser và một cửa sổ
Query cùng với kết quả truy vấn các cột CustomerID và CompanyName từ bảng
Customers.

Hình 1.35. Object Browser và Query window

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

- Chọn Execute từ menu Query


- Nhấn nút Execute Query trên thanh công cụ
- Nhấn phím F5 trên bàn phím
- Nhấn tổ hợp phím Ctrl + E
- Nhấn tổ hợp phím Alt + X
Khi chạy một truy vấn SQL, câu lệnh này được gửi đến cơ sở dữ liệu để thực thi.
Cơ sở dữ liệu thực thi lệnh và trả về kết quả. Các kết quả này được hiển thị lên phía
dưới của cửa sổ Query.

1.6.2.3. Lưu và mở lại một lệnh SQL


Bạn có thể lưu một lệnh SQL đã nhập trước đó trong Query Analyzer vào một tập
tin hoặc tải các lệnh SQL từ một tập tin để thực thi. Có 3 cách để lưu một lệnh SQL:
- Chọn Save hoặc Save As từ trình đơn File
- Nhấp chọn nút Save Query/Result trên thanh công cụ
- Nhấn tổ hợp phím Ctrl + S trên bàn phím
Nếu có một tập tin lưu trữ các lệnh SQL, bạn có thể mở và tải nó lên Query
Analyzer bằng cách:
- Chọn Open từ trình đơn File
- Nhấn nút Load SQL Script trên thanh công cụ.
- Nhấn tổ hợp phím Ctrl + Shift + P trên bàn phím.

1.7. Kết chương

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.

Bài tập chương 1

1. Cơ sở dữ liệu là gì? Phân biệt cơ sở dữ liệu với hệ quản trị cơ sở dữ liệu.


2. Trình bày các khái niệm thường gặp trong cơ sở dữ liệu.
3. SQL là gì? Trình bày các lệnh SQL thông dụng.
4. Trình bày cách tạo và sử dụng khung nhìn. Các ưu, nhược điểm của khung nhìn là
gì?
5. Trigger dùng để làm gì? Cách hoạt động, cú pháp tạo trigger?
6. Trình bày sự giống nhau và khác nhau giữa thủ tục và hàm? Hàm có ưu điểm nào
so với khung nhìn?
7. Thiết kế cơ sở dữ liệu cho hệ thống đăng ký học phần.
8. Thực hiện các yêu cầu sau dựa trên cơ sở dữ liệu đã thiết kế ở bài 7.
a. Tạo View cho phép xem danh sách sinh viên cùng với danh sách môn học mà
mỗi sinh viên đăng ký
b. Viết các thủ tục cần thiết để thêm, xóa, cập nhật thông tin một sinh viên, một
học phần và bảng đăng ký học phần.
c. Viết thủ tục để lấy danh sách các môn học mà sinh viên SV đăng ký
d. Viết thủ tục để lấy danh sách sinh viên đăng ký môn học MH
e. Viết thủ tục để tính tổng số tiền mà sinh viên SV phải đóng. Giả thiết học phí
trên 1 tín chỉ lý thuyết là 35.000 và 1 tín chỉ thực hành là 50.000

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.

2.1. Các lớp kết nối và không kết nối


Để cung cấp khả năng truy xuất cơ sở dữ liệu qua phương pháp kết nối và không
kết nối, ADO.NET định nghĩa hai nhóm lớp: lớp kết nối (hay lớp trình cung cấp -
managed provider) và lớp không kết nối (hay lớp dữ liệu - generic data)
Các lớp kết nối được dùng để tạo kết nối trực tiếp tới cơ sở dữ liệu hoặc để đồng bộ
hóa dữ liệu được lưu trên máy cục bộ với cơ sở dữ liệu. Có thể sử dụng các lớp này để
đọc các dòng dữ liệu từ cơ sở dữ liệu theo một hướng. Các lớp kết nối được sử dụng
tùy thuộc vào loại cơ sở dữ liệu mà bạn muốn kết nối tới.
Các lớp trong nhóm không kết nối được dùng để lưu trữ bản sao thông tin lấy được
từ cơ sở dữ liệu. Bản sao này được lưu trong bộ nhớ của máy tính mà chương trình
ứng dụng đang chạy. Lớp không kết nối thông dụng nhất là System.Data.DataSet. Các
lớp trong nhóm không kết nối không phụ thuộc vào loại cơ sở dữ liệu mà bạn dùng,
nghĩa là các lớp này được dùng chung cho dữ liệu được lấy từ cơ sở dữ liệu SQL

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.

2.2. Các lớp kết nối – The managed provider classes


Các đối tượng thuộc lớp kết nối cho phép bạn truy xuất trực tiếp đến cơ sở dữ
liệu. Chúng được dùng để kết nối, đọc và cập nhật thông tin trong cơ sở dữ liệu. hình
2.1 minh họa một vài đối tượng thuộc lớp kết nối và mối quan hệ giữa chúng.

Hình 2.1. Các lớp kết nối

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…

2.2.1. Lớp Connection


Có 3 lớp kết nối: SqlConnection, OledbConnection và OdbcConnection. Đối tượng
SqlConnection được dùng để kết nối tới cơ sở dữ liệu SQL Server. OledbConnection
được dùng để kết nối tới cơ sở dữ liệu có hỗ trợ chuẩn OLE DB như MS Access hay
Oracle. OdbcConnection dùng để kết nối tới cơ sở dữ liệu có hỗ trợ ODBC.
Nói chung, muốn giao tiếp với một cơ sở dữ liệu, trước hết phải thực hiện một kết
nối tới cơ sở dữ liệu thông qua lớp Connection.

2.2.2. Lớp Command


Có 3 lớp thực thi lệnh: SqlCommand, OledbCommand và OdbcCommand. Đối
tượng Command được dùng để thực thi một lệnh SQL như SELECT, INSERT,
UPDATE hay DELETE. Cũng có thể dùng đối tượng Command để gọi một thủ tục
hay lấy các dòng dữ liệu từ một bảng cụ thể. Đối tượng Command sử dụng một đối
tượng Connection và yêu cầu kết nối này phải được mở trước khi thực thi truy vấn.

2.2.3. Lớp Parameter


Có 3 lớp tham số (Parameter): SqlParameter, OledbParameter và OdbcParameter.
Đối tượng Parameter được dùng để gửi một tham số vào trong đối tượng Command.
Nó được dùng để lưu giá trị tham số được gửi vào trong một thủ tục (Stored
Procedure) hay một lệnh SQL. Khi có nhiều tham số, chúng được lưu và truyền vào
đối tượng Command thông qua đối tượng ParameterCollection.

2.2.4. Lớp ParameterCollection


Có 3 lớp ParameterCollection: SqlParameterCollection, OledbParameter-
Collection và OdbcParameterCollection. Đối tượng ParameterCollection được dùng để
lưu tập tham số muốn gửi vào đối tượng Command.

Trang 75
Giáo trình lập trình cơ sở dữ liệu

2.2.5. Lớp đọc dữ liệu tuần tự - DataReader


Có 3 lớp DataReader: SqlDataReader, OleDbDataReader và OdbcDataReader. Đối
tượng này dùng để dọc các dòng dữ lấy được từ cơ sở dữ liệu bởi đối tượng Command.
Các đối tượng DataReader chỉ được dùng để đọc dữ liệu theo một chiều từ đầu tới
cuối (dòng đầu tiên đến dòng cuối cùng) và không thể cập nhật dữ liệu cho các dòng
trong cơ sở dữ liệu.
Đối tượng DataReader có thể dùng để thay thế cho DataSet. Tuy nhiên, việc đọc dữ
liệu từ DataReader thường nhanh hơn đọc dữ liệu từ DataSet.

2.2.6. Các lớp điều phối dữ liệu - DataAdapter


Có 3 lớp DataAdapter: SqlDataAdapter, OleDbDataAdapter và OdbcDataAdapter.
Đối tượng DataAdapter được dùng để chuyển các dòng trong một cơ sở dữ liệu vào
một DataSet hoặc để đồng bộ dữ liệu đã thay đổi ở máy cục bộ tới cơ sở dữ liệu. Việc
đồng bộ được thực hiện qua một đối tượng Connection.
Bạn có thể đọc các dòng trong cơ sở dữ liệu vào một DataSet qua một
DataAdapter, thay đổi dữ liệu đó sau đó cập nhật các thay đổi lên cơ sở dữ liệu thông
qua đối tượng Connection.

2.2.7. Các lớp tạo lệnh truy vấn - CommandBuilder


Có 3 lớp CommandBuilder: SqlCommandBuilder, OledbCommandBuilder và
OdbcCommandBuilder. Đối tượng CommandBuilder tự động sinh ra các lệnh
INSERT, UPDATE, DELETE trên một bảng và đồng bộ dữ liệu bị thay đổi trong
DataSet lên cơ sở dữ liệu. Việc đồng bộ dữ liệu được thực hiện thông qua đối tượng
DataAdapter.

2.2.8. Các lớp giao dịch - Transaction


Có 3 lớp giao dịch (transaction): SqlTransaction, OledbTransaction và OdbcTran-
saction. Đối tượng Transaction được dùng để biểu diễn một giao dịch cơ sở dữ liệu
(database transaction). Một giao dịch cơ sở dữ liệu là một nhóm các lệnh làm thay đổi
các dòng trong cơ sở dữ liệu. Các lệnh lệnh này được xem như một đơn vị công việc
về mặt logic.
Ví dụ: trong một giao dịch ngân hàng, bạn muốn rút tiền từ một tài khoản và
chuyển nó sang một tài khoản khác. Khi đó, cả hai thay đổi đều phải được cập nhật
(commit) nếu giao dịch thành công hoặc cả hai phải được hủy bỏ (roll back) nếu có sự
cố.
2.2.9. Namespace chứa các lớp kết nối
Các lớp kết nối sử dụng cho SQL Server được khai báo trong namespace
System.Data.SqlClient. Các lớp kết nối cho cơ sở dữ liệu hỗ trợ OLE DB được khai

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.

2.3. Các lớp không kết nối – Generic Data Classes


Trong phần trước, bạn sử dụng các đối tượng kết nối để kết nối tới cơ sở dữ liệu
thông qua đối tượng Connection, thực thi lệnh SQL thông qua đối tượng Command,
đọc và lấy dữ liệu bởi đối tượng DataReader. Tuy nhiên, bạn chỉ có thể đọc theo một
hướng và luôn phải kết nối tới cơ sở dữ liệu.
Các đối tượng không kết nối (hay các đối tượng chứa dữ liệu) cho phép bạn lưu trữ
một bản sao thông tin lấy từ cơ sở dữ liệu. Việc này cho phép bạn làm việc với các
thông tin ngay cả khi đã ngắt kết nối tới cơ sở dữ liệu. Với các đối tượng không kết
nối, bạn có thể đọc các dòng theo thứ tự bất kỳ, có thể tìm kiếm, sắp xếp hay trích lọc
các dòng một cách linh hoạt. Thậm chí có thể tạo ra các thay đổi trên dữ liệu, sau đó
đồng bộ (cập nhật) các thay đổi này vào cơ sở dữ liệu.
Hình 2.2 cho thấy một số lớp không kết nối và mối quan hệ giữa chúng. Cầu nối
giữa các lớp kết nối và các lớp không kết nối là DataAdapter. Nó được dùng để cập
nhập dữ liệu bị thay đổi từ DataSet vào cơ sở dữ liệu.

2.3.1. Lớp DataSet


Đối tượng của lớp DataSet được dùng để biểu diễn một bản sao thông tin được lưu
trong cơ sở dữ liệu. Ta có thể thay đổi dữ liệu trong bản sao này và sau đó cập nhật
những thay đổi đó vào cơ sở dữ liệu qua đối tượng kết nối DataAdapter. Một DataSet
có thể biểu diễn các cấu trúc như một cơ sở dữ liệu bao gồm các bảng, các dòng và
cột. Ngoài ra, ta có thể thêm các ràng buộc giữa các bảng được lưu trong DataSet như
tính duy nhất (unique) hoặc rằng buộc về khóa ngoại.
Đối tượng DataSet cũng được dùng để chứa dữ liệu có định dạng XML. Trên thực
tế, tất cả các thông tin chứa trong DataSet đều được biểu diễn ở dạng XML, kể cả
thông tin lấy được từ cơ sở dữ liệu.

2.3.2. Lớp DataTable


Đối tượng của lớp DataTable dùng để biểu diễn một bảng. Một DataSet có thể
chứa nhiều bảng hay nhiều đối tượng DataTable. Những bảng này được truy xuất qua
thuộc tính Tables của lớp DataSet. Thuộc tính này có kiểu DataTableCollection.

2.3.3. Lớp DataRow


Đối tượng có kiểu DataRow dùng để biểu diễn một hàng (một dòng trong
DataTable). Một DataTable có thể chứa nhiều dòng hay nhiều đối tượng DataRow.

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.

2.3.4. Lớp DataColumn


Đối tượng thuộc lớp DataColumn được dùng để biểu diễn một cột của bảng. Một
DataTable có thể chứa nhiều cột hay nhiều đối tượng DataColumn. Các cột của bảng
được truy xuất thông qua thuộc tính Columns của lớp DataTable. Thuộc tính này có
kiểu DataColumnCollection.

Hình 2.2. Các lớp không kết nối

2.3.5. Lớp ràng buộc - Constraint


Đối tượng thuộc kiểu Constraint dùng để biểu diễn một ràng buộc cơ sở dữ liệu áp
dụng cho một hoặc nhiều đối tượng DataColumn của một bảng (DataTable). Một
DataTable có thể lưu trữ nhiều đối tượng Constraint. Các ràng buộc này được truy

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.3.6. Lớp DataView


Đối tượng có kiểu DataView được dùng để xem nội dung dữ liệu của các dòng
trong một DataTable bằng cách dùng một bộ lọc với điều kiện nào đó.

2.3.7. Lớp quan hệ - DataRelation


Đối tượng DataRelation dùng để biểu diễn một quan hệ giữa hai đối tượng
DataTable. Có thể dùng một đối tượng DataRelation làm quan hệ cha-con giữa hai
bảng như trong cơ sở dữ liệu. Một DataSet có thể chứa nhiều đối tượng DataRelation.
Các quan hệ này được truy xuất qua thuộc tính Relations của lớp DataSet. Thuộc tính
này có kiểu DataRelationCollection.

2.3.8. Namespace chứa các lớp không kết nối


Các lớp DataSet, DataTable, DataRow, DataColumn, DataRelation, Constraint và
DataView được khai báo trong namespace System.Data. Namespace này còn chứa
nhiều lớp khác mà bạn sẽ sử dụng trong các phần sau.

2.4. Sử dụng ADO.Net để thực thi một truy vấn SQL


Phần này trình bày các bước thực hiện và cách sử dụng các lớp kết nối và không
kết nối để thực thi một câu lệnh truy vấn SQL. Chương trình ví dụ sử dụng các lớp kết
nối và không kết nối để thực hiện một câu lệnh SELECT lấy 10 dòng dữ liệu trên bảng
Customers, nhận giá trị trên các cột CustomerID, CompanyName, ContactName và
Address. Sau đó, lưu trữ các dòng này vào một đối tượng DataSet.
Trang 79
Giáo trình lập trình cơ sở dữ liệu

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.

2.4.2. Chi tiết thực hiện truy vấn bởi ADO.Net

Bước 1: Tạo chuỗi kết nối


Khi kết nối đến một cơ sở dữ liệu SQL Server, cần phải cung cấp một chuỗi kết nối
đến máy mà SQL Server đang chạy. Chuỗi kết nối gồm những thành phần sau:
- Tên của máy mà SQL Server đang chạy. Giá trị này được đặt sau từ khóa server
của chuỗi kết nối. Nếu SQL Server đang chạy trên máy cục bộ, bạn có thể dùng
từ khóa localhost thay cho tên server. Ví dụ: server=localhost.
- Tên của cơ sở dữ liệu. Giá trị này được đặt sau từ khóa database của chuỗi kết
nối. Ví dụ: database=Northwind.
- Tên của người dùng được phép kết nối đến cơ sở dữ liệu. Giá trị này được đặt
sau từ khóa uid hoặc User ID. Ví dụ: uid=sa.
- Mật khẩu tương ứng với người dùng cơ sở dữ liệu (ở bước trước). Giá trị này
được đặt sau từ khóa pwd hoặc Password. Ví dụ: pwd=sa.
- Các thành phần của chuỗi kết nối phải phân tách nhau bởi dấu chấm phẩy.

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";

Bước 2: Tạo đối tượng kết nối


Sử dụng lớp SqlConnection để tạo một đối tượng kết nối tới cơ sở dữ liệu SQL
Server bằng cách gửi biến chuỗi kết nối ở bước 1 qua tham số của phương thức tạo
lập.
SqlConnection mySqlConnection =
new SqlConnection(connectionString);

Bước 3: Viết lệnh truy vấn


Khai báo một biến kiểu chuỗi chứa câu lệnh SELECT để lấy dữ liệu 10 dòng đầu
tiên trên các cột CustomerID, CompanyName, ContactName và Address của bảng
Customers.
string selectString =
"SELECT TOP 10 CustomerID, CompanyName, ContactName, Address "+
"FROM Customers " +
"ORDER BY CustomerID";

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.

Bước 4: Tạo đối tượng SqlCommand


Để tạo đối tượng SqlCommand, bạn có thể dùng phương thức CreateCommand()
của lớp SqlConnection hoặc dùng phương thức tạo lập của lớp SqlCommand. Phương
thức CreateCommand() trả về một đối tượng SqlCommand mới dựa trên đối tượng kết
nối SqlConnection.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

Bước 5: Thiết lập lệnh truy vấn


Thuộc tính CommandText của lớp SqlCommand chứa câu lệnh SQL mà bạn muốn
thực hiện. Để thực thi lệnh truy vấn SELECT đã tạo ở bước 3, bạn gán biến này cho
thuộc tính CommandText.
mySqlCommand.CommandText = selectString;

Bước 6: Tạo đối tượng SqlDataAdapter


Đối tượng SqlDataAdapter được dùng để truyền thông tin từ cơ sở dữ liệu về đối
tượng DataSet.
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();

Trang 81
Giáo trình lập trình cơ sở dữ liệu

Bước 7: Thiết lập lệnh truy vấn cho SqlDataAdapter


Thuộc tính SelectCommand của lớp SqlDataAdapter chứa câu lệnh SELECT mà
bạn muốn thực thi. Ở đây, giá trị thuộc tính này được đặt là đối tượng SqlCommand
mà bạn đã tạo ở bước 4. Điều này cho phép thực hiện câu lệnh SELECT đã được định
nghĩa trong đối tượng SqlCommand.
mySqlDataAdapter.SelectCommand = mySqlCommand;

Bước 8: Tạo đối tượng DataSet để lưu kết quả


Đối tượng DataSet được dùng để lưu trữ bản sao thông tin lấy được từ cơ sở dữ
liệu. Đoạn mã sau minh họa cách tạo một DataSet mới.
DataSet myDataSet = new DataSet();

Bước 9: Mở kết nối


Sử dụng phương thức Open() của lớp SqlConnection để mở kết nối tới cơ sở dữ
liệu. Một khi kết nối đã được mở, bạn có thể thực thi các truy xuất đến cơ sở dữ liệu
đó.
mySqlConnection.Open();

Bước 10: Lấy dữ liệu bởi phương thức Fill


Phương thức Fill của lớp SqlDataAdapter được dùng để lấy các dòng từ cơ sở dữ
liệu và lưu chúng vào một bảng (DataTable) trong một DataSet. Đây là một phương
thức ở dạng quá tải hàm (nghĩa là phương thức này có nhiều dạng). Nó chấp nhận hai
tham số:
 Đối tượng DataSet
 Tên của đối tượng DataTable để lưu dữ liệu (DataTable này được tạo tự động).
Phương thức Fill() tạo ra một DataTable trong DataSet với tên được chỉ ra trong
tham số thứ 2, sau đó thực thi câu lệnh SELECT. DataTable được tạo trong DataSet sẽ
xử lý các dòng trả về từ câu lệnh SELECT sau khi được thực thi. Từ đây, bạn có thể
truy xuất các dòng trong bảng mà không cần phải kết nối tới cơ sở dữ liệu.

mySqlDataAdapter.Fill(myDataSet, "Customers");

Bước 11: Đóng kết nối


Phương thức Close() của đối lớp SqlConnection được dùng để đóng kết nối tới cơ
sở dữ liệu sau khi thực thi xong truy vấn. Dĩ nhiên, không nhất thiết phải đóng kết nối
để đọc dữ liệu từ DataSet. Việc đóng kết nối ở đây để cho thấy rằng chúng ta có thể xử
lý dữ liệu được lưu trong bộ nhớ mà không cần tới kết nối cơ sở dữ liệu nữa.
mySqlConnection.Close();

Bước 12: Lấy bảng dữ liệu được lưu trong DataSet

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.

DataTable myDataTable = myDataSet.Tables["Customers"];

Bước 13: Xuất dữ liệu từ DataTable


Để xuất dữ liệu của từng dòng trong DataTable, ta dùng một đối tượng DataRow
để duyệt truy xuất lần lượt qua các dòng của DataTable. Lớp DataTable định nghĩa
một thuộc tính tên Rows có kiểu DataRowCollection – trả về tập các dòng được lưu
trong DataTable. Bạn có thể dùng thuộc tính Rows trong vòng lặp for each để duyệt
qua từng đối tượng DataRow như sau:

foreach (DataRow myDataRow in myDataTable.Rows)


{
// ... access the myDataRow object
}

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.

Chương trình 2.1: Truy vấn dữ liệu dùng lệnh SELECT

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
{

public static void Main()


{

// step 1: formulate a string containing the details of the


// database connection

string connectionString =
"server=localhost;database=Northwind;uid=sa;pwd=sa";

// step 2: create a SqlConnection object to connect to the


// database, passing the connection string to the constructor

SqlConnection mySqlConnection =
new SqlConnection(connectionString);

// step 3: formulate a SELECT statement to retrieve the


// CustomerID, CompanyName, ContactName, and Address
// columns for the first ten rows from the Customers table

string selectString =
"SELECT TOP 10 CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"ORDER BY CustomerID";

// step 4: create a SqlCommand object to hold the SELECT statement

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// step 5: set the CommandText property of the SqlCommand object to


// the SELECT string

mySqlCommand.CommandText = selectString;

// step 6: create a SqlDataAdapter object

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();

// step 7: set the SelectCommand property of the SqlAdapter object


// to the SqlCommand object

mySqlDataAdapter.SelectCommand = mySqlCommand;

// step 8: create a DataSet object to store the results of


// the SELECT statement

Trang 84
Giáo trình lập trình cơ sở dữ liệu

DataSet myDataSet = new DataSet();

// step 9: open the database connection using the


// Open() method of the SqlConnection object

mySqlConnection.Open();

// step 10: use the Fill() method of the SqlDataAdapter object to


// retrieve the rows from the table, storing the rows locally
// in a DataTable of the DataSet object

Console.WriteLine("Retrieving rows from the Customers table");


mySqlDataAdapter.Fill(myDataSet, "Customers");

// 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

DataTable myDataTable = myDataSet.Tables["Customers"];

// step 13: display the columns for each row in the DataTable,
// using a DataRow object to access each row in the DataTable

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("==========================================");
}

}
}

Kết quả thu được khi chạy chương trình.


Retrieving rows from the Customers table
CustomerID = ALFKI
CompanyName = Alfreds Futterkiste
ContactName = Maria Anders
Address = Obere Str. 57
================================================
CustomerID = ANATR
CompanyName = Ana Trujillo 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 = AROUT

Trang 85
Giáo trình lập trình cơ sở dữ liệu

CompanyName = Around the Horn


ContactName = Thomas Hardy
Address = 120 Hanover Sq.
================================================
CustomerID = BERGS
CompanyName = Berglunds snabbköp
ContactName = Christina Berglund
Address = Berguvsvägen 8
================================================
CustomerID = BLAUS
CompanyName = Blauer See Delikatessen
ContactName = Hanna Moos
Address = Forsterstr. 57
================================================
CustomerID = BLONP
CompanyName = Blondesddsl père et fils
ContactName = Frédérique Citeaux
Address = 24, place Kléber
================================================
CustomerID = BOLID
CompanyName = Bólido Comidas preparadas
ContactName = Martín Sommer
Address = C/ Araquil, 67
================================================
CustomerID = BONAP
CompanyName = Bon app'
ContactName = Laurence Lebihan
Address = 12, rue des Bouchers
================================================
CustomerID = BOTTM
CompanyName = Bottom-Dollar Markets
ContactName = Elizabeth Lincoln
Address = 23 Tsawassen Blvd.
================================================

2.5. Kết hợp Windows Form với ADO.Net


Ở phần trước, ta đã biết cách xây dựng một chương trình chạy ở chế độ dòng lệnh
có kết nối tới cơ sở dữ liệu thông qua ADO.Net. Trong phần này, ta sẽ làm quen với
việc sử dụng một điều khiển của Windows Form (chẳng hạn như DataGridView) kết
hợp với ADO.Net để lấy danh sách các khách hàng trong bảng Customers và hiển thị
lên màn hình.
Các điều khiển Windows Form thông dụng

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

Hiển thị dữ liệu dùng DataGridView


Trong phần này ta sẽ tìm hiểu cách sử dụng một điều khiển Windows Form có tên
DataGridView để truy xuất các dòng trong một cơ sở dữ liệu và hiển thị nó đó lên màn
hình ở dạng một bảng dữ liệu.

Trang 87
Giáo trình lập trình cơ sở dữ liệu

Hình 2.3. Xây dựng Form với DataGridView

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

Hình 2.4. DataGridView sau khi thay đổi kích thước

- Để thêm một điều khiển DataGridView vào Form UsingDataGridView, chọn


menu View  Toolbox. Trong nhóm Data của khung Toolbox, chọn điều khiển
DataGridView rồi kéo nó vào Form.

Trang 88
Giáo trình lập trình cơ sở dữ liệu

Hình 2.5. Tạo kết nối tới SQL Server

- 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.

Hình 2.6. Tạo kết nối tới SQL Server

- Mở Server Explorer (Vào menu View chọn Server Explorer)

Trang 89
Giáo trình lập trình cơ sở dữ liệu

Hình 2.7. Kết nối DataGridView với 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).

Hình 2.8. Data Source Configuration Wizard

- Ở 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

Hình 2.9. Chọn cơ sở dữ liệu kết nối

- 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.

Hình 2.10. Chọn bảng dữ liệu nguồn

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

Hình 2.12. Kết quả chạy chương trình

2.6. Kết chương

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.

Bài tập chương 2

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.

Những vấn đề sẽ được đề cập trong chương này


 Kết nối cơ sở dữ liệu SQL Server dùng SqlConnection
 Tổ hợp kết nối – Connection Pooling
 Quản lý trạng thái kết nối
 Quản lý các sự kiện khi kết nối
 Tạo đối tượng Connection với Visual Studio .Net

3.1. Lớp SqlConnection


Để kết nối tới một cơ sở dữ liệu SQL Server, ta dùng một đối tượng của lớp
SqlConnection. Đối tượng này quản lý và điều khiển việc liên lạc giữa cơ sở dữ liệu và
chương trình ứng dụng. Lớp SqlConnection chỉ được dùng cho SQL Server nhưng hầu
hết các thuộc tính, phương thức và sự kiện trong lớp này cũng giống như trong các lớp
OledbConnection và OdbcConnection.

PROPERTY TYPE DESCRIPTION


ConnectionString string Cho phép lấy hay gán chuỗi thông tin kết
nối tới cơ sở dữ liệu.
ConnectionTimeout int Trả về thời gian chờ (tính bằng giây) trong
khi chương trình cố gắng thiết lập một kết
nối tới cơ sở dữ liệu. giá trị mặc định là
15 giây.
Database string Trả về tên của cơ sở dữ liệu đã hoặc đang
kết nối.
DataSource string Trả về tên của máy server đang chạy SQL
Server chứa cơ sở dữ liệu.
PacketSize int Trả về kích thước (tính theo byte) của các
gói tin trong mạng dùng để liên lạc với SQL
Server. Thuộc tính này chỉ áp dụng cho
SqlConnection.Giá trị mặc định là 8192
bytes.

Trang 95
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


ServerVersion string Trả về chuỗi chứa phiên bản của SQL Server.
State ConnectionState Trả về trạng thái hiện tại của kết nối:
Broken. Closed, Connecting, Executing,
Fetching hay Open.
WorkstationId string Trả về chuỗi chứa thông tin máy khách đang
chạy ứng dụng kết nối tới SQL Server. Thuộc
tính này cũng chỉ được áp dụng cho
SqlConnection.
Bảng 3.1. Các thuộc tính của lớp SqlConnection

METHOD RETURN TYPE DESCRIPTION


BeginTransaction() SqlTransaction Hàm dùng để thông báo bắt đầu một giao
dịch.
ChangeDatabase() void Thay đổi cơ sở dữ liệu muốn kết nối.
Close() void Đóng kết nối tới cơ sở dữ liệu.
CreateCommand() SqlCommand Tạo và trả về một đối tượng thực thi lệnh
SqlCommand.
Open() void Mở một kết nối cơ sở dữ liệu với các thông
tin thiết lập kết nối được chỉ ra trong
thuộc tính ConnectionString.
Bảng 3.2. Các phương thức của lớp SqlConnection
Ta cũng có thể sử dụng các sự kiện để cho phép một đối tượng phát đi các thông
điệp đến một đối tượng khác nhằm thông báo có điều gì đó đã xảy ra. Chẳng hạn, khi
nhấp chuột lên một ứng dụng Windows, một sự kiện xảy ra hay còn gọi là phát sinh
(fired).
EVENT EVENT HANDLER DESCRIPTION
StateChange StateChangeEventHandler Phát sinh khi trạng thái kết nối bị
thay đổi.
InfoMessage SqlInfoMessageEventHandler Phát sinh khi cơ sở dữ liệu trả về một
cảnh báo hay một thông điệp nào đó.
Bảng 3.3. Các sự kiện của lớp SqlConnection.

3.2. Kết nối cơ sở dữ liệu SQL Server bởi SqlConnection


Để tạo một đối tượng SqlConnection, ta dùng phương thức tạo lập SqlConnection()
của nó. Phương thức này ở dạng quá tải hàm, nghĩa là có nhiều hàm cùng tên nhưng
khác nhau về cách gọi hàm và truyền tham số. Các dạng quá tải của hàm này là:
SqlConnection ()
SqlConnection (string connectionString)

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";

SqlConnection mySqlConnection = SqlConnection (connectionString);


Hoặc đơn giản hơn là
Trang 96
Giáo trình lập trình cơ sở dữ liệu

SqlConnection mySqlConnection = SqlConnection


("server=localhost;database=Northwind;uid=sa;pwd=sa");
Hoặc
SqlConnection mySqlConnection = new SqlConnection();

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

3.3. Kết nối tới cơ sở dữ liệu Access và Oracle


Trước khi chuyển sang tìm hiểu cách mở một kết nối, quản lý trạng thái và xử lý sự
kiện kết nối, ta khảo sát sơ lược cách để tạo kết nối tới cơ sở dữ liệu MS Access và
Oracle. Để chương trình có thể tương tác với những cơ sở dữ liệu này, ta dùng các lớp
trong namespace System.Data.OleDb. Namespace này chứa các lớp được dùng cho
các cơ sở dữ liệu có hỗ trợ OLE DB (Object Linking and Embedding for Databases)
như Access và Oracle.

3.3.1. Kết nối cơ sở dữ liệu Access


Để kết nối tới một cơ sở dữ liệu Access, ta dùng đối tượng OleDbConnection - thay
vì SqlConnection ở mục trước – với chuỗi kết nối có dạng sau:
provider=Microsoft.Jet.OLEDB.4.0;data source=databaseFile

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.

Chương trình 3.1: Sử dụng OleDbConnection để kết nối cơ sở dữ liệu Access


/*
OleDbConnectionAccess.cs illustrates how to use an
OleDbConnection object to connect to an Access database
*/

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";

// create an OleDbConnection object to connect to the


// database, passing the connection string to the constructor

OleDbConnection myOleDbConnection =
new OleDbConnection(connectionString);

// create an OleDbCommand object


OleDbCommand myOleDbCommand = myOleDbConnection.CreateCommand();

// set the CommandText property of the OleDbCommand object to


// a SQL SELECT statement that retrieves a row from the Customers table
myOleDbCommand.CommandText =
"SELECT CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";

// open the database connection using the


// Open() method of the OleDbConnection object

myOleDbConnection.Open();

// create an OleDbDataReader object and call the ExecuteReader()


// method of the OleDbCommand object to run the SELECT statement

OleDbDataReader myOleDbDataReader = myOleDbCommand.ExecuteReader();

// read the row from the OleDbDataReader object using


// the Read() method

myOleDbDataReader.Read();

// display the column values

Console.WriteLine("myOleDbDataReader[\"CustomerID\"] = " +
myOleDbDataReader["CustomerID"]);
Console.WriteLine("myOleDbDataReader[\"CompanyName\"] = " +
myOleDbDataReader["CompanyName"]);
Console.WriteLine("myOleDbDataReader[\"ContactName\"] = " +
myOleDbDataReader["ContactName"]);
Console.WriteLine("myOleDbDataReader[\"Address\"] = " +
myOleDbDataReader["Address"]);

// close the OleDbDataReader object using the Close() method

Trang 99
Giáo trình lập trình cơ sở dữ liệu

myOleDbDataReader.Close();

// close the OleDbConnection object using the Close() method

myOleDbConnection.Close();
}
}

Kết quả khi chạy chương trình:

myOleDbDataReader["CustomerID"] = ALFKI
myOleDbDataReader["CompanyName"] = Alfreds Futterkiste
myOleDbDataReader["ContactName"] = Maria Anders
myOleDbDataReader["Address"] = Obere Str. 57

3.3.2. Kết nối cơ sở dữ liệu Oracle


Để kết nối tới một cơ sở dữ liệu Oracle, ta cũng dùng đối tượng OleDbConnection
với chuỗi kết nối có dạng:
provider=MSDAORA;data source=OracleNetServiceName;user id=username;
password=password

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.

3.4. Mở và đóng kết nối


Một khi đã tạo được đối tượng Connection và đặt giá trị thích hợp cho thuộc tính
ConnectionString, bạn cần phải mở kết nối tới cơ sở dữ liệu trước khi thực thi một
lệnh truy vấn nào đó. Điều này được thực hiện bằng cách gọi hàm Open() của đối
tượng Connection như sau:

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";

// create a SqlConnection object to connect to the


// database, passing the connection string to the constructor

SqlConnection mySqlConnection =
new SqlConnection(connectionString);

// open the database connection using the


// Open() method of the SqlConnection object

mySqlConnection.Open();

// display the properties of the SqlConnection object

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);

// close the database connection using the Close() method


// of the SqlConnection object

mySqlConnection.Close();
}
}

Kết quả sau khi chạy chương trình:


mySqlConnection.ConnectionString =
server=localhost;database=Northwind;uid=sa;
mySqlConnection.ConnectionTimeout = 15
mySqlConnection.Database = Northwind
mySqlConnection.DataSource = localhost
mySqlConnection.PacketSize = 8192
mySqlConnection.ServerVersion = 08.00.0194
mySqlConnection.State = Open
mySqlConnection.WorkstationId = JMPRICE-DT1

3.5. Tổ hợp kết nối – Connection Pooling


Việc mở và đóng một kết nối tới cơ sở dữ liệu là quá trình tương đối tốn thời gian.
Vì thế, ADO.Net tự động lưu các kết nối cơ sở dữ liệu vào một tổ hợp (pool). Tổ hợp
kết nối nhằm cải thiện hiệu suất một cách đáng kể vì bạn không cần phải chờ khi tạo
một kết nối mới tới cơ sở dữ liệu đã từng thiết lập kết nối trước đó. Khi đóng kết nối
bởi phương thức Close, nó không thực sự đóng hẳn, thay vào đó, kết nối sẽ được đánh
dấu là không được sử dụng và được lưu vào tổ hợp. Như vậy, nó luôn sẵn sàng cho lần
kết nối sau.
Sau đó, nếu có một yêu cầu kết nối với cùng một thông tin (chẳng hạn như cùng
tên cơ sở dữ liệu, tên đăng nhập và mật khẩu,…), hệ thống sẽ tìm trong tổ hợp một kết
nối đang rảnh và trả về kết nối đó. Bạn sẽ dùng kết nối này để truy xuất đến cơ sở dữ
liệu.
Khi sử dụng đối tượng SqlConnection, có thể chỉ ra số kết nối tối đa, tối thiểu được
phép lưu trong tổ hợp bằng cách thiết lập giá trị cho thuộc tính max pool size, min
pool size trong chuỗi kết nối. Giá trị mặc định cho max pool size là 100 và min pool
size là 0.
Ví dụ sau tạo một đối tượng kết nối SqlConnection với số kết nối tối đa trong tổ
hợp là 10 và số kết nối tối thiểu là 5.
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa;" +
"max pool size=10;min pool size=5");

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.

Chương trình 3.3: Connection Pooling


/*
ConnectionPooling.cs illustrates connection pooling
*/

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"
);

// open the SqlConnection object 10 times

for (int count = 1; count <= 10; count++)


{
Console.WriteLine("count = " + count);

// create a DateTime object and set it to the


// current date and time

DateTime start = DateTime.Now;

// open the database connection using the


// Open() method of the SqlConnection object

mySqlConnection.Open();

// subtract the current date and time from the start,


// storing the difference in a TimeSpan

TimeSpan timeTaken = DateTime.Now - start;

// display the number of milliseconds taken to open


// the connection

Console.WriteLine("Milliseconds = " + timeTaken.Milliseconds);

// display the connection state

Trang 103
Giáo trình lập trình cơ sở dữ liệu

Console.WriteLine("mySqlConnection.State = " +
mySqlConnection.State);

// close the database connection using the Close() method


// of the SqlConnection object

mySqlConnection.Close();
}
}
}

Kết quả sau khi chạy chương trình


count = 1
Milliseconds = 101
mySqlConnection.State = Open

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.

3.6. Quản lý trạng thái kết nối


Trạng thái của kết nối cho biết quá trình yêu cầu kết nối tới cơ sở dữ liệu. Hai trạng
thái dễ nhận biết nhất là Open và Closed. Để biết trạng thái hiện tại của một kết nối tới
cơ sở dữ liệu, ta dùng thuộc tính State của đối đối tượng SqlConnection. Thuộc tính
này trả về một hằng số có kiểu liệt kê (enum) ConnectionState. Một enum là một danh
sách các hằng số, mỗi hằng số được đặt một tên.
Bảng sau liệt kê các hằng số được định nghĩa trong enum ConnectionState.
CONSTANT NAME DESCRIPTION
Broken Kết nối đã bị ngắt vì một lý do nào đó. Điều này có thể xảy
ra sau khi bạn mở kết nối. Để khắc phục, ta gọi hàm đóng kết
nối và rồi mở lại kết nối đó.
Closed Kết nối đã bị đóng.
Connecting Đang thiết lập kết nối tới cơ sở dữ liệu.
Executing Kết nối đang được dùng trong quá trình thực thi một lệnh
truy vấn.
Fetching Kết nối đang được dùng trong quá trình nhận thông tin trả về
từ cơ sở dữ liệu.
Open Kết nối đang mở.
Bảng 3.4. Các trạng thái kết nối

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();
}

3.7. Quản lý sự kiện trong quá trình kết nối


Các lớp Connection có hai sự kiện: StateChange và InfoMessage.

3.7.1. Sự kiện StateChange


Sự kiện này phát sinh khi trạng thái của kết nối bị thay đổi. Bạn cũng có thể sử
dụng sự kiện này để quản lý các thay đổi về trạng thái của đối tượng Connection.
Phương thức dùng để xử lý một sự kiện gọi là một trình xử lý sự kiện (event
handler). Nó được gọi khi một sự kiện được phát sinh. Mọi phương thức để xử lý sự
kiện đều phải có kiểu trả về là void và chấp nhận hai tham số đầu vào.

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.

Chương trình 3.4: Sử dụng sự kiện StateChange


/*
StateChange.cs illustrates how to use the StateChange event
*/

using System;
using System.Data;
using System.Data.SqlClient;

class StateChange
{
// define the StateChangeHandler() method to handle the
// StateChange event

public static void StateChangeHandler(


object mySender, StateChangeEventArgs myEvent )
{
Console.WriteLine(
"mySqlConnection State has changed from " +
myEvent.OriginalState + " to " +
myEvent.CurrentState

Trang 106
Giáo trình lập trình cơ sở dữ liệu

);
}

public static void Main()


{
// create a SqlConnection object

SqlConnection mySqlConnection = new


SqlConnection("server=localhost;database=Northwind;uid=sa;pwd=sa");

// monitor the StateChange event using the StateChangeHandler() method

mySqlConnection.StateChange +=
new StateChangeEventHandler(StateChangeHandler);

// open mySqlConnection, causing the State to change from Closed


// to Open

Console.WriteLine("Calling mySqlConnection.Open()");
mySqlConnection.Open();

// close mySqlConnection, causing the State to change from Open


// to Closed

Console.WriteLine("Calling mySqlConnection.Close()");
mySqlConnection.Close();
}
}

Kết quả sau khi chạy chương trình:


Calling mySqlConnection.Open()
mySqlConnection State has changed from Closed to Open
Calling mySqlConnection.Close()
mySqlConnection State has changed from Open to Closed

3.7.2. Sự kiện InfoMessage


Sự kiện này phát sinh khi cơ sở dữ liệu trả về một cảnh báo hoặc một thông điệp
được sinh ra bởi cơ sở dữ liệu. Sự kiện InfoMessage được dùng để quản lý các thông
điệp này. Để lấy thông điệp, ta đọc nội dung thuộc tính Errors của tham số
SqlInfoMessageEventArgs. Bạn có thể sinh ra các thông báo lỗi hoặc thông tin nào đó
bằng cách dùng mệnh đề RAISERROR hoặc PRINT của SQL Server.
Phương thức InfoMessageHandler() sau được dùng để xử lý sự kiện InfoMessage.
Thuộc tính Errors của tham số thứ hai SqlInfoMessageEventArgs chứa các thông điệp
phát sinh bởi cơ sở dữ liệu.
public static void InfoMessageHandler(
object mySender, SqlInfoMessageEventArgs myEvent)
{
Console.WriteLine(
"The following message was produced:\n" +
myEvent.Errors[0]

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.

Chương trình 3.5: Sử dụng sự kiện InfoMessage


/*
InfoMessage.cs illustrates how to use the InfoMessage event
*/
using System;
using System.Data;
using System.Data.SqlClient;

class InfoMessage
{
// define the InfoMessageHandler() method to handle the
// InfoMessage event

public static void InfoMessageHandler(


object mySender, SqlInfoMessageEventArgs myEvent )
{
Console.WriteLine(
"The following message was produced:\n" +
myEvent.Errors[0]
);
}

public static void Main()


{
// create a SqlConnection object

SqlConnection mySqlConnection = new


SqlConnection("server=localhost;database=Northwind;uid=sa;pwd=sa");

// monitor the InfoMessage event using the InfoMessageHandler() method

mySqlConnection.InfoMessage +=
new SqlInfoMessageEventHandler(InfoMessageHandler);

// open mySqlConnection
mySqlConnection.Open();

// create a SqlCommand object


SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// run a PRINT statement


mySqlCommand.CommandText =
"PRINT 'This is the message from the PRINT statement'";
mySqlCommand.ExecuteNonQuery();

// run a RAISERROR statement

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();
}
}

Kết quả chạy chương trình:


The following message was produced:
System.Data.SqlClient.SqlError: This is the message from the PRINT
statement
The following message was produced:
System.Data.SqlClient.SqlError: This is the message from the
RAISERROR statement

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

Hình 3.1. Tạo đối tượng SqlConnection với VS.Net

Hình 3.2. Thêm các thành phần vào Toolbox


Trang 110
Giáo trình lập trình cơ sở dữ liệu

Để thiết lập chuỗi kết


nối đến cơ sở dữ liệu, bạn
có thể nhập trực tiếp chuỗi
này vào ô nhập bên cạnh
thuộc tính Connection-
String hoặc nhấp chuột vào
DropDownList (hình 3.3)
rồi chọn New Connection
để mở hộp thoại Add
Connection (như hình 3.4).
- Trong ô DataSource,
chọn Microsoft SQL
Server (SqlClient). Trong ô
Server name, chọn tên máy
đang chạy SQL Server.
- Trong mục Logon to the
server, chọn chế độ chứng
thực.
- Chọn Select or enter a
database name rồi chọn tên
cơ sở dữ liệu muốn kết nối
vào khung nằm bên dưới.
- Nhấn nút Test
Connection để kiểm tra kết
nối tới cơ sở dữ liệu có
thành công hay không. Sau
đó nhấn nút OK.
- Để thay đổi một số tùy
Hình 3.4. Tạo kết nối đến SQL Server chọn khác, nhấp chọn nút
Advanced.
Cửa sổ Advanced Properties (hình 3.5) chứa tất cả các tùy chọn liên quan đến đối
tượng kết nối như tên người dùng, mật khẩu, chế độ chứng thực, max pool size, min
pool size, connection timeout, packet size,… Sau khi hoàn tất, bạn sẽ thấy chuỗi kết
nối xuất hiện trong khung Properties có dạng như sau:
data source=localhost;initial catalog=Northwind;persist security
info=False;user id=sa;pwd=sa;workstation id=phucnv;packet size=4096

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

Hình 3.5. Cửa sổ Advanced Properties

3.9. Tạo trình xử lý sự kiện bằng Visual Studio .Net


Bạn có thể thêm các đoạn mã để xử lý một sự kiện thông qua VS.Net. Chẳng hạn
như thêm hàm xử lý sự kiện StateChange cho đối tượng sqlConnection1 đã tạo trong
phần trước. Để làm được điều này, thực hiện theo các bước sau:
- Chọn đối tượng sqlConnection1 nằm ở dưới
Form. Nhấp phải chuột, chọn Properties.
- Trong khung Properties, chọn nút Events. Hình
3.6 cho thấy các sự kiện của đối tượng
sqlConnection1.
- Nhấp đôi chuột lên tên của sự kiện trong cửa
sổ Properties để thêm mã xử lý cho sự kiện đó.
Trong ví dụ này, ta nhấp đôi chuột lên sự kiện
StateChange. VS.Net hiển thị màn hình viết
mã và tạo sẵn một phương thức xử lý sự kiện
(hình 3.7). Ta sẽ thêm mã xử lý cho phương
Hình 3.6. Các sự kiện của
thức này.
SqlConnection

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 );
}

Hình 3.7. Phương thức xử lý sự kiện StateChange được tạo tự động

Trang 113
Giáo trình lập trình cơ sở dữ liệu

Hình 3.8. Phương thức xử lý sự kiện StateChange hoàn chỉnh.

3.10. Kết chương

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.

Bài tập chương 3

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.

Những vấn đề chính sẽ được trình bày trong chương này


 Lớp SqlCommand
 Cách sử dụng đối tượng SqlCommand để thực thi các lệnh SQL
 Thực thi các lệnh SELECT và TableDirect
 Thực thi một lệnh để lấy dữ liệu ở dạng XML
 Thực thi các lệnh làm thay đổi thông tin trong cơ sở dữ liệu.
 Các giao dịch trong cơ sở dữ liệu.
 Truyền tham số cho các lệnh
 Thực thi các thủ tục SQL Server
 Tạo một đối tượng Command bằng Visual Studio .Net

4.1. Lớp SqlCommand


Đối tượng SqlCommand được sử dụng để thực thi một lệnh truy vấn nào đó tới cơ sở
dữ liệu SQL Server. Tương tự, các đối tượng OleDbCommand và OdbcCommand dùng
để thực thi một truy vấn trên cơ sở dữ liệu có hỗ trợ OLE DB (như Access hay Oracle)
và ODBC. Bảng 4.1 và 4.2 liệt kê một số thuộc tính và phương thức của lớp
SqlCommand.
PROPERTY TYPE DESCRIPTION
CommandText string Cho phép lấy hay gán chuỗi lệnh SQL,
tên thủ tục hay tên bảng muốn lấy dữ
liệu (dùng trong TableDirect
command).
CommandTimeout int Lấy hay gán số giây chờ để cố gắng
thực thi một lệnh. Giá trị mặc định
là 30 giây.
CommandType CommandType Lấy hay gán một giá trị chỉ ra cho
đối tượng Command biết dữ liệu trong
thuộc tính CommandText được hiểu như
thế nào. Các giá trị hợp lệ được gán
cho thuộc tính này là

Trang 116
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


CommandType.Text, CommandType
.StoredProcedure, and CommandType
.TableDirect. Text chỉ ra rằng giá
trị trong CommandText là một lệnh
SQL. StoredProcedure chỉ ra rằng
CommandText đang chứa tên của một thủ
tục. TableDirect có nghĩa CommandText
là tên của bảng mà bạn muốn lấy tất
cả các dòng và các cột. Giá trị mặc
định cho CommandType là Text.
Connection string Lấy tên của kết nối cơ sở dữ liệu.
DesignTimeVisible bool Lấy hay gán giá trị cho biết đối
tượng Command có được hiển thị lên
Windows Form trong lúc thiết kế hay
không. Giá trị mặc định là false.
Parameters SqlParameterCollection Danh sách các tham số (nếu có) được
truyền vào Command. Đây là thuộc tính
chỉ đọc.
Transaction SqlTransaction Lấy hay gán giao dịch cơ sở dữ liệu
của lệnh.
UpdatedRowSource UpdateRowSource Lấy hay gán giá trị cho biết cách mà
kết quả của lệnh được áp dụng cho một
DataRow khi phương thức Update() của
đối tượng DataAdapter được gọi.
Bảng 4.1. Các thuộc tính của lớp SqlCommand

METHOD RETURN TYPE DESCRIPTION


Cancel() void Hủy bỏ việc thực thi một lệnh.
CreateParameter() SqlParameter Tạo một tham số mới cho đối tượng
Command.
ExecuteNonQuery() int Thực thi một lệnh truy vấn SQL (bao gồm
các lệnh INSERT, UPDATE, DELETE, các
lệnh định nghĩa cấu trúc cơ sở dữ liệu
hay các Stored Procedure không trả về
bảng kết quả) nhưng không trả về tập kết
quả. Hàm trả về một số nguyên cho biết
số dòng bị ảnh hưởng khi thực thi lệnh
truy vấn.
ExecuteReader() SqlDataReader Thực thi các lệnh SELECT, TableDirect
hay các Stored Procedure có trả về bảng
kết quả. Tập kết quả trả về được lưu
trong một đối tượng DataReader.
ExecuteScalar() object Thực thi các lệnh SQL có trả về duy nhất
một giá trị, những giá trị thừa bị loại
bỏ. Kết quả trả về là một đối tượng
(kiểu object).
ExecuteXmlReader() XmlReader Thực thi lệnh SELECT có trả về dữ liệu
XML. Tập dữ liệu kết quả được lưu trong
một đối tượng XmlReader. Phương thức này
chỉ có trong lớp SqlCommand.
Prepare() void Tạo ra một phiên bản khác của lệnh sẵn
sàng cho truy vấn, làm cho việc thực thi
nhanh hơn.
ResetCommandTimeout() void Đặt lại giá trị của thuộc tính

Trang 117
Giáo trình lập trình cơ sở dữ liệu

METHOD RETURN TYPE DESCRIPTION


CommandTimeout về giá trị mặc định.
Bảng 4.2. Các phương thức trong lớp SqlCommand.

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. Tạo đối tượng SqlCommand


Có hai cách để tạo một đối tượng SqlCommand: dùng phương thức khởi tạo của lớp
SqlCommand hoặc gọi phương thức CreateCommand() của đối tượng SqlConnection.
Bạn cũng có thể dùng cùng một cách cho đối tượng OleDbCommand hay
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);

4.2.2. Tạo đối tượng SqlCommand bằng phương trức CreateCommand


Thay vì tạo đối tượng SqlCommand dùng phương thức khởi tạo, ta dùng phương
thức CreateCommand() của đối tượng SqlConnection. Phương thức này trả về một đối
tượng SqlCommand mới.
Ví dụ:
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

Đối tượng mySqlCommand sẽ sử dụng mySqlConnection để liên lạc với cơ sở dữ liệu.

4.3. Thực thi các lệnh SELECT và TableDirect


Lệnh TableDirect thực ra chỉ là một lệnh SELECT trả về tất cả các dòng, các cột của
một bảng nào đó. Đối tượng Command có ba phương thức được dùng để thực thi một
câu lệnh SELECT hay TableDirect.
METHOD RETURN TYPE DESCRIPTION
ExecuteReader() SqlDataReader Được dùng để thực thi các lệnh SELECT,
TableDirect hay lời gọi thủ tục có trả về
một tập dữ liệu. Kết quả trả về này được
lưu trong một đối tượng DataReader.
ExecuteScalar() object Dùng để thực thi các lệnh SELECT chỉ trả về
một giá trị (những giá trị khác sẽ bị bỏ
qua). Kết quả trả về được lưu trong một
object.
ExecuteXmlReader() XmlReader Được dùng để thực thi các lệnh SELECT và
trả về dữ liệu XML. Kết quả trả về được lưu
trong một đối tượng XmlReader. Phương thức
này chỉ áp dụng cho lớp SqlCommand.

Trang 119
Giáo trình lập trình cơ sở dữ liệu

Bảng 4.4. Những phương thức lấy thông tin từ cơ sở dữ liệu.

4.3.1. Phương thức ExecuteReader


Phương thức này thực thi một lệnh SELECT và kết quả trả về được lưu trong một
đối tượng DataReader. Sau đó, bạn có thể dùng đối tượng này để đọc các dòng trả về từ
cơ sở dữ liệu.
Ví dụ: Đoạn mã sau tạo các đối tượng cần thiết và thực thi một câu lệnh SELECT để
lấy ra 5 dòng đầu tiên trong bảng Customers.
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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"]);

Console.WriteLine("mySqlDataReader[\" CompanyName\"] = " +

Trang 120
Giáo trình lập trình cơ sở dữ liệu

mySqlDataReader["CompanyName"]);

Console.WriteLine("mySqlDataReader[\" ContactName\"] = " +


mySqlDataReader["ContactName"]);

Console.WriteLine("mySqlDataReader[\" Address\"] = " +


mySqlDataReader["Address"]);
}

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.

Chương trình 4.1: Thực thi lệnh SELECT dùng SqlCommand


/*
ExecuteSelect.cs illustrates how to execute a SELECT
statement using a SqlCommand object
*/
using System;
using System.Data;
using System.Data.SqlClient;

class ExecuteSelect
{

public static void Main()


{

// create a SqlConnection object to connect to the database

SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

// create a SqlCommand object

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// set the CommandText property of the SqlCommand object to


// the SELECT statement

mySqlCommand.CommandText =
"SELECT TOP 5 CustomerID, CompanyName, ContactName, Address "
+
"FROM Customers " +
"ORDER BY CustomerID";

// open the database connection using the


// Open() method of the SqlConnection object

mySqlConnection.Open();

// create a SqlDataReader object and call the ExecuteReader()


// method of the SqlCommand object to run the SQL SELECT
// statement

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();

Trang 121
Giáo trình lập trình cơ sở dữ liệu

// read the rows from the SqlDataReader object using


// the Read() method

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();
}

// close the SqlDataReader object using the Close() method


mySqlDataReader.Close();

// close the SqlConnection object using the Close() method


mySqlConnection.Close();
}
}

Kết quả chạy chương trình


mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Alfreds Futterkiste
mySqlDataReader["ContactName"] = Maria Anders
mySqlDataReader["Address"] = Obere Str. 57

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

4.3.2. Điều khiển cách xử lý (Command Behavior) của phương thức


ExecuteReader
Phương thức ExecuteReader chấp nhận một tham số (tùy chọn) để điều khiển cách
thức xử lý lệnh và kết quả. Giá trị của tham số này có kiểu enum
System.Data.CommandBehavior. Bảng 4.5 liệt kê các giá trị có trong enum
CommandBehavior và ý nghĩa của chúng.
VALUE DESCRIPTION
CloseConnection Chỉ ra rằng, khi đối tượng DataReader bị đóng bởi phương
thức Close(), đối tượng kết nối (Connection) mà Command sử
dụng cũng bị đóng theo.
Default Có nghĩa là đối tượng Command có thể trả về nhiều tập kết
quả.
KeyInfo Yêu cầu đối tượng Command trả về thông tin của các cột khóa
chính trong tập kết quả.
SchemaOnly Cho biết đối tượng Command chỉ trả về thông tin của các cột.
SequentialAccess Cho phép đối tượng DataReader đọc các dòng có các cột chứa
dữ liệu nhị phân kích thước lớn. SequentialAccess buộc
DataReader đọc dữ liệu ở dạng stream. Để đọc tiếp dữ liệu từ
Stream này, ta dùng phương thức GetBytes() hay GetChars()
của DataReader.
SingleResult Cho biết đối tượng Command chỉ trả về một tập kết quả.
SingleRow Cho biết đối tượng Command chỉ trả về một dòng.
Bảng 4.5. Các giá trị trong enum CommandBehavior

4.3.2.1. Sử dụng CommandBehavior.SingleRow


Giá trị SingleRow được dùng khi muốn đối tượng Command chỉ trả về một dòng
duy nhất.
Ví dụ: Giả sử ta có một đối tượng Command tên là mySqlCommand với thuộc tính
CommandText được gán giá trị như sau:
mySqlCommand.CommandText =
"SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice " +
"FROM Products";

Tiếp theo, ta gọi phương thức ExecuteReader với tham số là CommandBehavior.


SingleRow để cho đối tượng Command chỉ lấy dòng kết quả đầu tiên.
SqlDataReader mySqlDataReader =
mySqlCommand.ExecuteReader(CommandBehavior.SingleRow);

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.

Chương 4.2: Điều khiển cách xử lý của đối tượng Command


/*
SingleRowCommandBehavior.cs illustrates how to control
the command behavior to return a single row
*/
using System;

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice " +
"FROM Products";
mySqlConnection.Open();

// pass the CommandBehavior.SingleRow value to the


// ExecuteReader() method, indicating that the Command object
// only returns a single row

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();
}
}

Kết quả chạy chương trình


mySqlDataReader["ProductID"] = 1
mySqlDataReader["ProductName"] = Chai
mySqlDataReader["QuantityPerUnit"] = 10 boxes x 20 bags
mySqlDataReader["UnitPrice"] = 18

Trang 124
Giáo trình lập trình cơ sở dữ liệu

4.3.2.2. Sử dụng CommandBehavior.SchemaOnly


Giá trị SchemaOnly được sử dụng khi muốn đối tượng Command chỉ trả về thông
tin của các cột lấy được bởi câu lệnh SELECT hoặc tất cả các cột nếu dùng lệnh
TableDirect.
Ví dụ: Giả sử ta có một đối tượng Command tên là mySqlCommand với thuộc tính
CommandText được gán giá trị như sau:
mySqlCommand.CommandText =
"SELECT ProductID, ProductName, UnitPrice " +
"FROM Products " +
"WHERE ProductID = 1";

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
{

public static void Main()


{

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

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT ProductID, ProductName, UnitPrice " +
"FROM Products " +
"WHERE ProductID = 1";

mySqlConnection.Open();

// pass the CommandBehavior.SchemaOnly constant to the


// ExecuteReader() method to get the schema

SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader(CommandBehavior.SchemaOnly);

// read the DataTable containing the schema from the DataReader

DataTable myDataTable = productsSqlDataReader.GetSchemaTable();

// display the rows and columns in the DataTable

foreach (DataRow myDataRow in myDataTable.Rows)


{
Console.WriteLine("\nNew column details follow:");

foreach (DataColumn myDataColumn in myDataTable.Columns)


{

Console.WriteLine(myDataColumn + " = " +


myDataRow[myDataColumn]);

if (myDataColumn.ToString() == "ProviderType")
{
Console.WriteLine(myDataColumn + " = " +
((System.Data.SqlDbType)myDataRow[myDataColumn]));
}
}
}

productsSqlDataReader.Close();
mySqlConnection.Close();
}
}

Kết quả chạy chương trình


ColumnName = ProductID
ColumnOrdinal = 0
ColumnSize = 4
NumericPrecision = 0
NumericScale = 0
IsUnique =
IsKey =
BaseCatalogName =
BaseColumnName = ProductID
BaseSchemaName =
BaseTableName =

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

New column details follow:


ColumnName = ProductName
ColumnOrdinal = 1
ColumnSize = 40
NumericPrecision = 0
NumericScale = 0
IsUnique =
IsKey =
BaseCatalogName =
BaseColumnName = ProductName
BaseSchemaName =
BaseTableName =
DataType = System.String
AllowDBNull = False
ProviderType = 12
ProviderType = NVarChar
IsAliased =
IsExpression =
IsIdentity = False
IsAutoIncrement = False
IsRowVersion =
IsHidden =
IsLong = False
IsReadOnly = False

New column details follow:


ColumnName = UnitPrice
ColumnOrdinal = 2
ColumnSize = 8
NumericPrecision = 0
NumericScale = 0
IsUnique =
IsKey =
BaseCatalogName =
BaseColumnName = UnitPrice
BaseSchemaName =
BaseTableName =
DataType = System.Decimal
AllowDBNull = True
ProviderType = 9
ProviderType = Money
IsAliased =
IsExpression =
IsIdentity = False
IsAutoIncrement = False
IsRowVersion =

Trang 129
Giáo trình lập trình cơ sở dữ liệu

IsHidden =
IsLong = False
IsReadOnly = False

4.3.3. Thực thi lệnh TableDirect bởi phương thức ExecuteReader


Để lấy tất các dòng, các cột của một bảng nào đó, ta gán giá trị TableDirect cho
thuộc tính CommandType của đối tượng Command. Khi đó, thuộc tính CommandText
chứa tên của bảng muốn lấy dữ liệu.
Đối tượng SqlCommand không hỗ trợ chức năng thực thi lệnh TableDirect. Vì thế,
ví dụ trong phần này được thực hiện bởi đối tượng OleDbCommand.
Ngoài cách dùng đối tượng SqlConnection để kết nối tới SQL Server, ta cũng có thể
dùng đối tượng OleDbConnection để kết nối tới SQL Server. Bạn chỉ cần đặt giá trị cho
provider của chuỗi kết nối là SQLOLEDB và gửi chuỗi kết nối này vào tham số trong
phương thức khởi tạo của OleDbConnection.
OleDbConnection myOleDbConnection =
new OleDbConnection(
"Provider=SQLOLEDB;server=localhost;database=Northwind;" +
"uid=sa;pwd=sa"
);

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.

Chương 4.4: Thực thi lệnh TableDirect


/*
ExecuteTableDirect.cs illustrates how to execute a
TableDirect command
*/

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"
);

OleDbCommand myOleDbCommand = myOleDbConnection.CreateCommand();

// set the CommandType property of the OleDbCommand object to


// TableDirect

myOleDbCommand.CommandType = CommandType.TableDirect;

// set the CommandText property of the OleDbCommand object to


// the name of the table to retrieve from

myOleDbCommand.CommandText = "Products";
myOleDbConnection.Open();

OleDbDataReader myOleDbDataReader =
myOleDbCommand.ExecuteReader();

// only read the first 5 rows from the OleDbDataReader object

for (int count = 1; count <= 5; count++)


{
myOleDbDataReader.Read();

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

4.3.4. Thực thi lệnh SELECT dùng phương thức ExecuteScalar


Phương thức ExecuteScalar được dùng để thực thi một lệnh SQL SELECT và chỉ trả
về một giá trị, những giá trị khác được loại bỏ. Giá trị này nằm ở hàng đầu tiên, cột đầu
tiên trong bảng kết quả và có kiểu object.
Ví dụ: Sử dụng phương thức ExecuteScalar để thực thi một câu lệnh SELECT đếm
(COUNT) số dòng của bảng.
Trong ví dụ này, ta gán một lệnh SELECT có sử dụng hàm COUNT() cho thuộc tính
CommandText của đối tượng SqlCommand. Lệnh SELECT này sẽ trả về số dòng của
bảng Products trong cơ sở dữ liệu Northwind.
mySqlCommand.CommandText =
"SELECT COUNT(*) " +
"FROM Products";

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" );

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT COUNT(*) " +
"FROM Products";
mySqlConnection.Open();

// call the ExecuteScalar() method of the SqlCommand object


// to run the SELECT statement

int returnValue = (int)mySqlCommand.ExecuteScalar();

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

4.3.5. Lấy dữ liệu XML dùng phương thức ExecuteXMLReader


Phương thức ExecuteXMLReader được dùng để thực thi một câu lệnh SELECT trả
về dữ liệu ở định dạng XML. Kết quả trả về của phương thức này là một đối tượng
XmlReader, cho phép đọc dữ liệu XML nhận được từ cơ sở dữ liệu. Phương thức này
chỉ áp dụng cho lớp SqlCommand.
SQL Server mở rộng chuẩn SQL để cho phép truy vấn cơ sở dữ liệu và lấy dữ liệu ở
định dạng XML. Đặc biệt, ta có thể thêm mệnh đề FOR XML vào cuối lệnh SELECT
theo cú pháp:
FOR XML
{RAW | AUTO | EXPLICIT}
[, XMLDATA]
[, ELEMENTS]
[, BINARY BASE64]

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

ExecuteXmlReader.cs illustrates how to use the ExecuteXmlReader()


method to run a SELECT statement that returns XML
*/

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" );

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// set the CommandText property of the SqlCommand object to


// a SELECT statement that retrieves XML

mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID " +
"FOR XML AUTO";

mySqlConnection.Open();

// create a SqlDataReader object and call the ExecuteReader()


// method of the SqlCommand object to run the SELECT statement

XmlReader myXmlReader = mySqlCommand.ExecuteXmlReader();

// read the rows from the XmlReader object using the Read()
// method

myXmlReader.Read();
while (!myXmlReader.EOF)
{
Console.WriteLine(myXmlReader.ReadOuterXml());
}

myXmlReader.Close();
mySqlConnection.Close();

}
}

Kết quả khi chạy chương trình


<Products ProductID="1" ProductName="Chai" UnitPrice="18.0000"/>
<Products ProductID="2" ProductName="Chang" UnitPrice="19.0000"/>
<Products ProductID="3" ProductName="Aniseed Syrup" UnitPrice="10.0000"/>
<Products ProductID="4" ProductName="Chef Anton&apos;s Cajun Seasoning"
UnitPrice="22.0000"/>

Trang 135
Giáo trình lập trình cơ sở dữ liệu

<Products ProductID="5" ProductName="Chef Anton&apos;s Gumbo Mix"


UnitPrice="21.3500"/>

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.

4.4.1. Thực thi các lệnh INSERT, UPDATE và DELETE


4.4.1.1. Thêm dòng mới bằng lệnh INSERT
Để thực thi một lệnh INSERT bằng cách dùng phương thức ExecuteNonQuery,
trước hết, cần phải tạo một đối tượng Command
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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

4.4.1.2. Cập nhật dữ liệu bằng lệnh UPDATE


Để cập nhật dữ liệu cho dòng mới được thêm vào ở phần trước, ta dùng lệnh
UPDATE. Đoạn mã sau gán chuỗi lệnh UPDATE cho thuộc tính CommandText của đối
tượng Command để thay đổi dữ liệu trong cột CompanyName của dòng vừa được thêm
vào. Sau đó, gọi phương thức ExecuteNonQuery để thực thi lệnh UPDATE.
mySqlCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'New Company' " +
"WHERE CustomerID = 'J2COM'";

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.

4.4.1.3. Xóa dữ liệu bằng lệnh DELETE


Cuối cùng, để xóa dữ liệu, ta dùng lệnh DELETE như ví dụ sau:
mySqlCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = 'J2COM'";

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();
}

public static void Main()


{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );

// create a SqlCommand object and set its Commandtext property


// to an INSERT statement

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName" +
") VALUES (" +
" 'J2COM', 'Jason Price Corporation'" +
")";

mySqlConnection.Open();

// call the ExecuteNonQuery() method of the SqlCommand object


// to run the INSERT statement

int numberOfRows = mySqlCommand.ExecuteNonQuery();

Console.WriteLine("Number of rows added = " + numberOfRows);

DisplayRow(mySqlCommand, "J2COM");

// set the CommandText property of the SqlCommand object to


// an UPDATE statement

mySqlCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'New Company' " +
"WHERE CustomerID = 'J2COM'";

// call the ExecuteNonQuery() method of the SqlCommand object


// to run the UPDATE statement

numberOfRows = mySqlCommand.ExecuteNonQuery();

Console.WriteLine("Number of rows updated = " + numberOfRows);

DisplayRow(mySqlCommand, "J2COM");

Trang 138
Giáo trình lập trình cơ sở dữ liệu

// set the CommandText property of the SqlCommand object to


// a DELETE statement

mySqlCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = 'J2COM'";

// call the ExecuteNonQuery() method of the SqlCommand object


// to run the DELETE statement

numberOfRows = mySqlCommand.ExecuteNonQuery();

Console.WriteLine("Number of rows deleted = " + numberOfRows);

mySqlConnection.Close();
}
}

Kết quả khi chạy chương trình


Number of rows added = 1
mySqlDataReader["CustomerID"] = J2COM
mySqlDataReader["CompanyName"] = Jason Price Corporation

Number of rows updated = 1


mySqlDataReader["CustomerID"] = J2COM
mySqlDataReader["CompanyName"] = New Company

Number of rows deleted = 1

4.4.2. Thực thi lệnh DDL dùng phương thức ExecuteNonQuery


Ngoài các lệnh INSERT, UPDATE và DELETE, phương thức ExecuteNonQuery
còn được dùng để thực thi các lệnh định nghĩa cấu trúc cơ sở dữ liệu (DDL – Data
Define Language). Chẳng hạn như CREATE TABLE, CREATE INDEX.
Để thực thi các lệnh DDL, trước hết, ta cũng phải tạo một đối tượng Command.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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.

Ví dụ 2: Thêm một ràng buộc khóa ngoại cho bảng MyPersons.


mySqlCommand.CommandText =
"ALTER TABLE MyPersons " +
"ADD EmployerID nchar(5) CONSTRAINT FK_Persons_Customers " +
"REFERENCES Customers(CustomerID)";

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.

Ví dụ 3: Xóa một bảng dùng lệnh DROP


mySqlCommand.CommandText = "DROP TABLE MyPersons";
result = mySqlCommand.ExecuteNonQuery();

Đ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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// set the CommandText property of the SqlCommand object to


// a CREATE TABLE statement

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();

// call the ExecuteNonQuery() method of the SqlCommand object


// to run the CREATE TABLE statement

Console.WriteLine("Creating MyPersons table");

int result = mySqlCommand.ExecuteNonQuery();

Console.WriteLine("mySqlCommand.ExecuteNonQuery() = " + result);

// set the CommandText property of the SqlCommand object to


// an ALTER TABLE statement

mySqlCommand.CommandText =
"ALTER TABLE MyPersons " +
"ADD EmployerID nchar(5) CONSTRAINT FK_Persons_Customers " +
"REFERENCES Customers(CustomerID)";

// call the ExecuteNonQuery() method of the SqlCommand object


// to run the ALTER TABLE statement

Console.WriteLine("Altering MyPersons table");

result = mySqlCommand.ExecuteNonQuery();

Console.WriteLine("mySqlCommand.ExecuteNonQuery() = " + result);

// set the CommandText property of the SqlCommand object to


// a DROP TABLE statement

mySqlCommand.CommandText = "DROP TABLE MyPersons";

// call the ExecuteNonQuery() method of the SqlCommand object


// to run the DROP TABLE statement

Console.WriteLine("Dropping MyPersons table");

result = mySqlCommand.ExecuteNonQuery();

Console.WriteLine("mySqlCommand.ExecuteNonQuery() = " + result);

mySqlConnection.Close();
}
}

Kết quả khi chạy chương trình


Creating MyPersons table
mySqlCommand.ExecuteNonQuery() = -1

Trang 141
Giáo trình lập trình cơ sở dữ liệu

Altering MyPersons table


mySqlCommand.ExecuteNonQuery() = -1
Dropping MyPersons table
mySqlCommand.ExecuteNonQuery() = -1

4.5. Các giao dịch trong cơ sở dữ liệu


Trong chương 1, ta đã biết cách nhóm các lệnh SQL với nhau để tạo thành các giao
dịch (transaction). Giao dịch có thể được thực hiện thành công (commit) hoặc bị gỡ bỏ
(roll back) như một lệnh thông thường. Chẳng hạn, trong giao dịch ngân hàng, bạn có
thể rút tiền từ một tài khoản vào chuyển nó sang một tài khoản khác. Trong trường hợp
này, hoặc cả hai thay đổi phải được thực hiện thành công (commit) hoặc phải được gỡ
bỏ nếu một trong hai thao tác xảy ra lỗi (roll back). Trong phần này, ta sẽ làm quen với
việc sử dụng giao dịch cơ sở dữ liệu với ADO.NET.
Có 3 lớp giao dịch (Transaction): SqlTransaction, OleDbTransaction và Odbc-
Transaction. Các đối tượng thuộc lớp Transaction được dùng để biểu diễn một giao dịch
trong ADO.NET.
Ta xét một ví dụ về giao dịch gồm hai câu lệnh INSERT. Lệnh INSERT thứ nhất sẽ
thêm một dòng vào bảng Customers còn lệnh INSERT thứ hai thêm một dòng vào bảng
Orders. Dòng mới trong bảng Orders sẽ tham chiếu đến dòng mới trong bảng
Customers. Hai lệnh INSERT này được viết như sau:
INSERT INTO Customers ( CustomerID, CompanyName)
VALUES ( 'J3COM', 'Jason Price Corporation'

INSERT INTO Orders ( CustomerID ) VALUES ( 'J3COM' )

Để 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

Chương trình 4.9: Sử dụng giao dịch với ADO.NET


/*
ExecuteTransaction.cs illustrates the use of a transaction
*/

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();

// step 1: create a SqlTransaction object and start the


// transaction by calling the BeginTransaction() method of the
// SqlConnection object

SqlTransaction mySqlTransaction =
mySqlConnection.BeginTransaction();

// step 2: create a SqlCommand object to hold a SQL statement

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// step 3: set the Transaction property for the SqlCommand


object

mySqlCommand.Transaction = mySqlTransaction;

// step 4: set the CommandText property of the SqlCommand object


// to the first INSERT statement

mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName" +
") VALUES (" +
" 'J3COM', 'Jason Price Corporation'" +
")";

// step 5: run the first INSERT statement

Console.WriteLine("Running first INSERT statement");

mySqlCommand.ExecuteNonQuery();

// step 6: set the CommandText property of the SqlCommand object


// to the second INSERT statement

mySqlCommand.CommandText =
"INSERT INTO Orders (" +

Trang 143
Giáo trình lập trình cơ sở dữ liệu

" CustomerID" +
") VALUES (" +
" 'J3COM'" +
")";

// step 7: run the second INSERT statement

Console.WriteLine("Running second INSERT statement");

mySqlCommand.ExecuteNonQuery();

// step 8: commit the transaction using the Commit() method


// of the SqlTransaction object

Console.WriteLine("Committing transaction");

mySqlTransaction.Commit();
mySqlConnection.Close();
}
}

Kết quả khi chạy chương trình trên


Running first INSERT statement
Running second INSERT statement
Committing transaction

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.

4.6. Truyền tham số vào các lệnh


Trong các ví dụ trước đây, giá trị được truyền vào mỗi cột được gán cứng (hard-
code) trong các lệnh SQL. Chẳng hạn như lệnh INSERT sau
INSERT INTO Customers ( CustomerID, CompanyName )
VALUES ( 'J3COM', 'Jason Price Corporation' )

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.

Bước 1: Tạo đối tượng Command chứa lệnh SQL có tham số


Điều này có thể hiểu như sau: thay vì đưa một giá trị vào lệnh SQL, ta thay giá trị đó
bằng một điểm đánh dấu tham số. Một điểm như vậy đánh dấu vị trí mà ta sẽ đưa giá trị
vào đó.
Cú pháp sử dụng điểm đánh dấu trùy thuộc vào cơ sở dữ liệu đang sử dụng. Với
SQL Server, điểm đánh dấu được ký hiệu bởi một tên, bắt đầu bằng dấu @. Chẳng hạn
như @CustomerID, @CompanyName. Câu lệnh INSERT sau sử dụng 3 điểm đánh đấu
tham số tương ứng với giá trị trên 3 cột CustomerID, CompanyName và ContactName
của bảng Customers.
INSERT INTO Customers ( CustomerID, CompanyName, ContactName )
VALUES ( @CustomerID, @CompanyName, @ContactName )

Đ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

DELETE FROM Customers


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.

Bước 2: Thêm các tham số vào đối tượng Command


Trang 145
Giáo trình lập trình cơ sở dữ liệu

Để 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ự.

Bước 3: Truyền giá trị cho các tham số


Giá trị của mỗi tham số được gán qua thuộc tính Value. Để truy xuất đến một tham
số trong Command, ta đặt tên tham số trong cặp dấu móc vuông [ ] ngay sau thuộc tính
Parameters của đối tượng Command.
Đoạn mã sau minh họa cách gán giá trị cho các tham số:
mySqlCommand.Parameters["@CustomerID"].Value = "J4COM";
mySqlCommand.Parameters["@CompanyName"].Value = "J4 Company";
mySqlCommand.Parameters["@ContactName"].Value = "Jason Price";

Trang 147
Giáo trình lập trình cơ sở dữ liệu

Trong ví dụ này, giá trị của các tham số @CustomerID, @CompanyName và


@ContactName được gán lần lượt là J4COM, J4 Company và Jason Price. Những giá
trị này sẽ được thay thế vào các điểm đánh dấu của lệnh INSERT
INSERT INTO Customers ( CustomerID, CompanyName, ContactName )
VALUES ( 'J4COM', 'J4 Company', 'Jason Price' )

Để đơ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;

Bước 4: Thực thi lệnh


Để thực thi lệnh, ta sử dụng các phương thức thực thi của đối tượng Command như
ExecuteNonQuery, ExecuteScalar…
Nếu muốn thực thi các lệnh INSERT, UPDATE hay DELETE, ta dùng phương thức
ExecuteNonQuery. Nếu muốn thực thi lệnh SELECT, dùng các phương thức
ExecuteReader, ExecuteScalar hoặc ExecuteXmlReader.
Chương trình sau minh họa chi tiết cách truyền tham số vào một lệnh theo 4 bước trên.

Chương trình 4.10: Thực thi lệnh INSERT có tham số


/*
UsingParameters.cs illustrates how to run an INSERT
statement that uses parameters
*/

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();

// step 1: create a Command object containing a SQL statement


// with parameter placeholders

Trang 148
Giáo trình lập trình cơ sở dữ liệu

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName, ContactName" +
") VALUES (" +
" @CustomerID, @CompanyName, @ContactName" +
")";

// step 2: add parameters to the Command object

mySqlCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5);


mySqlCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 40);
mySqlCommand.Parameters.Add("@ContactName", SqlDbType.NVarChar, 30);

// step 3: set the parameters to specified values

mySqlCommand.Parameters["@CustomerID"].Value = "J4COM";
mySqlCommand.Parameters["@CompanyName"].Value = "J4 Company";
mySqlCommand.Parameters["@ContactName"].IsNullable = true;
mySqlCommand.Parameters["@ContactName"].Value = DBNull.Value;

// step 4: execute the command

mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Successfully added row to Customers table");

mySqlConnection.Close();
}
}

Kết quả khi chạy chương trình


Successfully added row to Customers table

4.7. Gọi các thủ tục SQL Server


Như đã trình bày trong những phần trước, hằng số CommandType.StoredProcedure
được gán cho thuộc tính CommandType của đối tượng Command cho biết lệnh cần
được thực thi là một thủ tục SQL (Stored Procedure). Tuy nhiên, cách thực sự không
hiệu quả bằng cách dùng lệnh T-SQL EXECUTE. Nếu dùng lệnh T-SQL EXECUTE, ta
có thể đọc các giá trị trả về bởi lệnh RETURN từ Stored Procedure. Điều này không thể
thực hiện được bằng cách thiết lập giá trị CommandType.StoredProcedure cho thuộc
tính CommandType.
Có hai cách để thực thi một thủ tục (stored procedure) phụ thuộc vào việc thủ tục có
trả về tập dữ liệu hay không. Tập dữ liệu kết quả (result set) là một hay nhiều dòng trả
về từ một bảng bởi câu lệnh SELECT.

4.7.1. Thực thi một Stored Procedure không trả về dữ liệu


Nếu một thủ tục không trả về dữ liệu, việc thực thi nó được thực hiện qua các bước sau

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ố.

4.7.2. Lấy dữ liệu trả về từ tham số dùng từ khóa OUTPUT


Đoạn mã SQL sau định nghĩa một thủ tục có tên AddProduct. Thủ tục này được
dùng để thêm một dòng vào bảng Products, sau đó trả về giá trị trên cột ProductID của
dòng mới qua một tham số ra (OUPUT Parameter). Tham số này có tên là
@MyProductID.
/*
AddProduct.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
in an OUTPUT parameter named @MyProductID
*/
CREATE PROCEDURE AddProduct
@MyProductID int OUTPUT,
@MyProductName nvarchar(40),
@MySupplierID int,
@MyCategoryID int,
@MyQuantityPerUnit nvarchar(20),
@MyUnitPrice money,
@MyUnitsInStock smallint,
@MyUnitsOnOrder smallint,
@MyReorderLevel smallint,
@MyDiscontinued bit
AS

-- insert a row into the Products table

INSERT INTO Products (


ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel,
Discontinued
) VALUES (
@MyProductName, @MySupplierID, @MyCategoryID, @MyQuantityPerUnit,
@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, @MyReorderLevel,
@MyDiscontinued
)

-- use the SCOPE_IDENTITY() function to get the last


-- identity value inserted into a table performed within

Trang 150
Giáo trình lập trình cơ sở dữ liệu

-- the current database session and stored procedure,


-- so SCOPE_IDENTITY returns the ProductID for the new row
-- in the Products table in this case

SELECT @MyProductID = SCOPE_IDENTITY()

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

"@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;

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.

Bước 3: Thực thi lệnh bởi phương thức ExecuteNonQuery


Để thực hiện việc gọi thủ tục với giá trị các tham số đã truyền vào, gọi phương thức
ExecuteNonQuery của đối tượng Command.
mySqlCommand.ExecuteNonQuery();

Bước 4: Đọc kết quả từ tham số


Bước cuối cùng là đọc các giá trị trả về được lưu trong các tham số ra. Để lấy được
dữ liệu từ những tham số này, ta dùng thuộc tính Value.
Chẳng hạn, đoạn mã sau dùng để hiển thị giá trị của tham số ProductID sinh ra bởi
lệnh thêm một dòng mới vào bảng Products.
Console.WriteLine("New ProductID = " +
mySqlCommand.Parameters["@MyProductID"].Value);

Đ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();

// step 1: create a Command object and set its CommandText


// property to an EXECUTE statement containing the stored
// procedure call

Trang 152
Giáo trình lập trình cơ sở dữ liệu

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"EXECUTE AddProduct @MyProductID OUTPUT, @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";

// step 2: add the required parameters to the Command object

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;

// step 3: execute the Command object using the


// ExecuteNonQuery() method

mySqlCommand.ExecuteNonQuery();

// step 4: read the value of the output parameter

Console.WriteLine("New ProductID = " +


mySqlCommand.Parameters["@MyProductID"].Value);

mySqlConnection.Close();
}
}

Trang 153
Giáo trình lập trình cơ sở dữ liệu

Kết quả khi chạy chương trình


New ProductID = 81

4.7.3. Lấy dữ liệu trả về bởi lệnh RETURN


Đoạn mã SQL sau định nghĩa một thủ tục có tên AddProduct2 tương tự như thủ tục
AddProduct nhưng dùng lệnh RETURN để trả về ProductID của dòng mới thay vì dùng
tham số OUTPUT.
/*
AddProduct2.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
*/
CREATE PROCEDURE AddProduct2
@MyProductName nvarchar(40),
@MySupplierID int,
@MyCategoryID int,
@MyQuantityPerUnit nvarchar(20),
@MyUnitPrice money,
@MyUnitsInStock smallint,
@MyUnitsOnOrder smallint,
@MyReorderLevel smallint,
@MyDiscontinued bit
AS

-- declare the @MyProductID variable

DECLARE @MyProductID int

-- insert a row into the Products table

INSERT INTO Products (


ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel,
Discontinued
) VALUES (
@MyProductName, @MySupplierID, @MyCategoryID, @MyQuantityPerUnit,
@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, @MyReorderLevel,
@MyDiscontinued
)

-- use the SCOPE_IDENTITY() function to get the last


-- identity value inserted into a table performed within
-- the current database session and stored procedure,
-- so SCOPE_IDENTITY returns the ProductID for the new row
-- in the Products table in this case

SET @MyProductID = SCOPE_IDENTITY()

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
{

public static void Main()


{

SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlConnection.Open();

// step 1: create a Command object and set its CommandText


// property to an EXECUTE statement containing the stored
// procedure call

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"EXECUTE @MyProductID = AddProduct2 @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";

// step 2: add the required parameters to the Command object

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;

// step 3: execute the Command object using the


// ExecuteNonQuery() method

mySqlCommand.ExecuteNonQuery();

// step 4: read the value of the output parameter

Console.WriteLine("New ProductID = " +


mySqlCommand.Parameters["@MyProductID"].Value);

mySqlConnection.Close();
}
}

4.7.4. Thực thi một Stored Procedure có trả về tập dữ liệu


Nếu thủ tục có trả về một tập dữ liệu, thực hiện theo các bước sau:
- B1. Tạo một đối tượng Command và gán lệnh EXECUTE theo sau bởi tên thủ
tục cho thuộc tính CommandText.
- B2. Thêm các tham số cần thiết vào đối tượng Command. Nhớ gán giá trị
ParameterDirection.Output cho thuộc tính Direction của các tham số nhận giá trị
trả về (OUTPUT parameter).
- B3. Thực thi lệnh dùng phương thức ExecuteReader, lưu kết quả trả về vào đối
tượng DataReader.

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

-- declare the @MyProductID variable


DECLARE @MyProductID int

-- insert a row into the Products table


INSERT INTO Products (
ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel,
Discontinued
) VALUES (
@MyProductName, @MySupplierID, @MyCategoryID, @MyQuantityPerUnit,
@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, @MyReorderLevel,
@MyDiscontinued
)

-- use the SCOPE_IDENTITY() function to get the last


-- identity value inserted into a table performed within
-- the current database session and stored procedure,
-- so SCOPE_IDENTITY returns the ProductID for the new row
-- in the Products table in this case

SET @MyProductID = SCOPE_IDENTITY()

Trang 157
Giáo trình lập trình cơ sở dữ liệu

-- return the result set

SELECT ProductName, UnitPrice


FROM Products
WHERE ProductID = @MyProductID

-- 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.

Chương trình 4.13: Gọi thủ tục SQL Server


/*
ExecuteAddProduct3.cs illustrates how to call the SQL Server
AddProduct3() stored procedure
*/

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();

// step 1: create a Command object and set its CommandText


// property to an EXECUTE statement containing the stored
// procedure call

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"EXECUTE @MyProductID = AddProduct3 @MyProductName, " +
"@MySupplierID, @MyCategoryID, @MyQuantityPerUnit, " +
"@MyUnitPrice, @MyUnitsInStock, @MyUnitsOnOrder, " +
"@MyReorderLevel, @MyDiscontinued";

// step 2: add the required parameters to the Command object

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;

// step 3: execute the Command object using the


// ExecuteReader() method

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();

// step 4: read the rows using the DataReader object

while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"ProductName\"] = " +
mySqlDataReader["ProductName"]);

Console.WriteLine("mySqlDataReader[\"UnitPrice\"] = " +
mySqlDataReader["UnitPrice"]);
}

// step 5: close the DataReader object

mySqlDataReader.Close();

// step 6: read the value of the output parameter

Console.WriteLine("New ProductID = " +


mySqlCommand.Parameters["@MyProductID"].Value);

mySqlConnection.Close();
}
}

Kết quả khi chạy chương trình


mySqlDataReader["ProductName"] = Widget
mySqlDataReader["UnitPrice"] = 5.99
New ProductID = 83

Trang 159
Giáo trình lập trình cơ sở dữ liệu

4.8. Tạo đối tượng Command bằng Visual Studio .Net


Để tạo một đối tượng Command bằng Visual Studio .Net, ta kéo một đối tượng
SqlCommand (hoặc OleDbCommand) từ thẻ Data của thanh Toolbox vào Form.
Đối tượng Command dùng một Connection làm phương tiện giao tiếp với cơ sở dữ
liệu. Vì vậy, trước khi tạo và cấu hình cho đối tượng Command, ta phải tạo một đối
tượng Connection qua các bước sau:
- B1. Tạo một dự án Windows Application mới
- B2. Thêm một đối tượng Connection vào dự án bằng cách kéo nó từ thẻ Data của
thanh Toolbox vào Form. Đối tượng này sẽ có tên mặc định là sqlConnection1
hay oleDbConnection1.
- B3. Cấu hình cho đối tượng Connection để truy cập đến cơ sở dữ liệu Northwind.
- B4. Kéo một đối tượng Command vào Form. Đối tượng này sẽ có tên mặc định
là sqlCommand1 hoặc oleDbCommand1.
- B5. Thiết lập thuộc tính Connection cho đối tượng Command bằng cách chọn đối
tượng Connection trong DropDownList bên phải thuộc tính Connection trong
khung Properties hoặc tạo một đối tượng Connection mới bằng cách chọn New từ
danh sách này.

Hình 4.1. Tạo đối tượng Command bằng VS.NET

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

Hình 4.3. Thêm một bảng vào truy vấn

Hình 4.4. Tạo truy vấn dùng Query Builder


Nhấn nút OK để kết thúc việc tạo truy vấn. Thuộc tính CommandText của đối tượng
sqlCommand1 sẽ chứa câu lệnh SELECT mà bạn vừa tạo.

4.9. Kết chương


Chương này trình bày các bước để thực thi các truy vấn tới cơ sở dữ liệu bằng cách
sử dụng lớp Command của ADO.NET. Có ba lớp Command được dùng: SqlCommand,
OleDbCommand và OdbcCommand. Đối tượng Command có thể thực thi một lệnh truy
vấn SQL như SELECT, INSERT, UPDATE, DELETE hoặc cũng có thể dùng để gọi
các thủ tục, lấy tất cả các dòng, các cột của một bảng (còn gọi là lệnh TableDirect).
Ngoài ra, chương này cũng hướng dẫn cách dùng một đối tượng SqlCommand để thực
thi lệnh SQL, dùng đối tượng SqlDataReader để đọc các dòng trả về từ cơ sở dữ liệu và
dùng đối tượng SqlTransaction để biểu diễn các giao dich cơ sở dữ liệu trong cơ sở dữ
liệu SQL Server.

Bài tập chương 4

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.

Những nội dung sẽ được đề cập trong chương


 Lớp SqlDataReader
 Tạo một đối tượng SqlDataReader
 Đọc dữ liệu từ SqlDataReader
 Lấy giá trị từ các cột với kiểu cụ thể
 Đọc các giá trị null
 Thực thi nhiều lệnh SQL
 Sử dụng DataReader trong Visual Studio .Net

5.1. Lớp SqlDataReader


Đối tượng SqlDataReader được dùng để đọc các dòng lấy được từ cơ sở dữ liệu
SQL Server. Tương tự, các đối tượng OleDbDataReader, OdbcDataReader dùng để
đọc dữ liệu từ các cơ sở dữ liệu hỗ trợ OLE DB, ODBC.
Các bảng dưới đây liệt kê một vài thuộc tính, phương thức được xây dựng trong lớp
SqlDataReader và ý nghĩa của chúng. Hầu hết các thuộc tính và phương thức này cũng
được xây dựng với cùng một tên trong các lớp OleDbDataAdapter và
OdbcDataAdapter.

PROPERTY TYPE DESCRIPTION


Depth int Lấy giá trị cho biết độ sâu của dòng hiện tại trong nếu
các dòng được truy vấn lồng nhau.
FieldCount int Lấy số cột của dòng hiện tại.
IsClosed bool Lấy giá trị cho biết đối tượng DataReader đã đóng hay
chưa.
RecordsAffected int Lấy số dòng được thêm vào, bị thay đổi hay bị xóa khi
thực hiện câu lệnh SQL.
Bảng 5.1. Các thuộc tính của SqlDataReader
Trang 164
Giáo trình lập trình cơ sở dữ liệu

METHOD RETURN TYPE DESCRIPTION


GetBoolean() bool Lấy giá trị của một cột có kiểu bit, trả về
kiểu Bool.
GetByte() byte Lấy giá trị của một cột có kiểu byte.
GetBytes() long Đọc các byte của một cột từ Stream vào một
mảng bytes. Trả về một số nguyên dài (kiểu
long) cho biết số byte đọc được.
GetChar() char Lấy một ký tự từ cột có kiểu char.
GetChars() long Đọc các ký tự của một cột từ Stream vào một
mảng ký tự. Trả về một số nguyên dài (kiểu
long) cho biết số ký tự đọc được.
GetDataTypeName() string Lấy tên kiểu dữ liệu (SQL Server) của một cột.
GetDateTime() DateTime Lấy giá trị của một cột có kiểu DateTime.
GetDecimal() decimal Lấy giá trị của một cột có kiểu Decimal.
GetDouble() double Lấy giá trị của một cột có kiểu số thực (số có
dấu chấm động), trả về giá trị kiểu double.
GetFieldType() Type Lấy kiểu dữ liệu (.Net Type) của một cột.
GetFloat() float Lấy giá trị của một cột có kiểu số thực (số có
dấu chấm động), trả về giá trị kiểu float.
GetGuid() Guid Trả về giá trị của cột có dạng globally unique
identifier (GUID).
GetInt16() short Lấy giá trị của một cột, trả về kiểu short.
GetInt32() int Lấy giá trị của một cột, trả về kiểu int.
GetInt64() long Lấy giá trị của một cột, trả về kiểu long.
GetName() string Lấy tên của một cột.
GetOrdinal() int Lấy vị trí (thứ tự) của một cột trong tập các
cột. cột đầu tiên được đánh dấu là 0.
GetSchemaTable() DataTable Trả về DataTable chứa thông tin chi tiết về
các cột được lưu trong DataReader.
GetSqlBinary() SqlBinary Lấy giá trị của một cột, trả về kiểu
SqlBinary. Lớp SqlBinary được khai báo trong
namespace System.Data.SqlTypes. Tất cả các
phương thức GetSql* chỉ được dùng cho lớp
SqlDataReader.
GetSqlBoolean() SqlBoolean Lấy giá trị của một cột, trả về kiểu
SqlBoolean.
GetSqlByte() SqlByte Lấy giá trị của một cột, trả về kiểu SqlByte.
GetSqlDateTime() SqlDateTime Lấy giá trị của một cột, trả về kiểu
SqlDateTime.
GetSqlDecimal() SqlDecimal Lấy giá trị của một cột, trả về kiểu
SqlDecimal.
GetSqlDouble() SqlDouble Lấy giá trị của một cột, trả về kiểu
SqlDouble.
GetSqlGuid() SqlGuid Lấy giá trị của một cột, trả về kiểu SqlGuid.
GetSqlInt16() SqlInt16 Lấy giá trị của một cột, trả về kiểu SqlInt16.
GetSqlInt32() SqlInt32 Lấy giá trị của một cột, trả về kiểu SqlInt32.
GetSqlInt64() SqlInt64 Lấy giá trị của một cột, trả về kiểu SqlInt64.
GetSqlMoney() SqlMoney Lấy giá trị của một cột, trả về kiểu SqlMoney.
GetSqlSingle() SqlSingle Lấy giá trị của một cột, trả về kiểu
SqlSingle.
GetSqlString() SqlString Lấy giá trị của một cột, trả về kiểu

Trang 165
Giáo trình lập trình cơ sở dữ liệu

METHOD RETURN TYPE DESCRIPTION


SqlString.
GetSqlValue() object Lấy giá trị của một cột, trả về kiểu object.
GetSqlValues() int Sao chép giá trị của các cột trong hàng hiện
tại vào một mảng các object. Hàm trả về một số
nguyên cho biết số phần tử của mảng.
GetString() string Lấy giá trị của một cột, trả về kiểu string.
GetValue() object Lấy giá trị của một cột, trả về kiểu object.
GetValues() int Sao chép giá trị của các cột trong hàng hiện
tại vào một mảng các object. Hàm trả về một số
nguyên cho biết số phần tử của mảng.
IsDBNull() bool Trả về một giá trị Bool cho biết cột có chứa
giá trị null hay không.
NextResult() bool Chuyển DataReader đến dòng tiếp theo trong tập
kết quả. Trả về giá trị Bool cho biết còn dòng
chưa được đọc hay không.
Read() bool Chuyển DataReader đến dòng tiếp theo trong ập
kết quả và đọc dữ liệu trên dòng đó. Trả về
giá trị Bool cho biết còn dòng chưa được đọc
hay không.
Bảng 5.2. Các phương thức của SqlDataReader
Chi tiết các cột của DataTable bao gồm tên cột (lưu trong cột ColumnName của
DataTable), thứ tự của cột (ColumnOrdinal), kích thước tối đa của giá trị được lưu
trong cột (ColumnSize), độ chính xác của số được lưu trong cột (NumericPrecision
NumericScale) và một vài thuộc tính khác. NumericPrecision là số lượng chữ số kể cả
bên trái và bên phải dấu chấm thập phân. NumericScale là số lượng chữ số nằm bên
phải dấu chấm thập phân.
Namespace System.Data.SqlTypes cung cấp các lớp đặc trưng cho các kiểu dữ liệu
được dùng trong SQL Server. Những lớp này cho phép lấy dữ liệu nhanh và an toàn hơn
so với các kiểu dữ liệu khác trả về bởi phương thức Get*. Việc sử dụng các lớp trong
namespace này giúp hạn chế lỗi chuyển đổi kiểu hoặc sai lệch kết quả về độ chính xác.
Vì các kiểu dữ liệu của .Net đều phải được chuyển qua lại với SqlTypes nên việc tạo và
sử dụng ngay các đối tượng từ các lớp trong namespace SqlTypes sẽ nhanh hơn.

5.2. Tạo đối tượng SqlDataReader


Đối tượng DataReader chỉ có thể được tạo bằng cách gọi phương thức
ExecuteReader của đối tượng Command.
Ví dụ: Đoạn mã sau tạo các đối tượng cần thiết và thực thi một câu lệnh SELECT
lấy về 5 dòng đầu tiên trong bảng Products của cơ sở dữ liệu Northwind trong SQL
Server. Sau đó, lưu kết quả vào đối tượng SqlDataReader.
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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.

5.3. Đọc dữ liệu từ SqlDataReader


Phương thức Read() được dùng để đọc các dòng từ một đối tượng DataReader.
Phương thức này trả về một giá trị Boolean. Giá trị true có nghĩa là còn một dòng khác
chưa được đọc, false nếu ngược lại.
Việc đọc giá trị trên một cột được thực hiện bằng cách đặt tên cột hoặc thứ tự của
cột vào cặp dấu móc vuông [ ] ngay sau đối tượng DataReader. Sự khác nhau giữa hai
cách đọc dữ liệu này là hiệu quả thực hiện. Việc sử dụng thứ tự hay chỉ số cột được
chương trình xử lý nhanh hơn so với cách dùng tên cột.
Chẳng hạn, để đọc giá trị trên cột CustomerID, ta viết productsSqlDataReader
[“ProductID”] hoặc productsSqlDataReader[0]. Vì ProductID là cột đầu tiên trong
bảng nên nó có thứ tự là 0.
Ví dụ:
Hai đoạn mã sau minh họa cách lấy giá trị của một cột từ DataReader bằng cách dùng
tên cột và chỉ số cột.
// Dùng tên cột
while (productsSqlDataReader.Read())
{
Console.WriteLine(productsSqlDataReader["ProductID"]);
Console.WriteLine(productsSqlDataReader["ProductName"]);
Console.WriteLine(productsSqlDataReader["UnitPrice"]);
Console.WriteLine(productsSqlDataReader["Discontinued"]);
}

// Dùng chỉ số cột


while (productsSqlDataReader.Read())
{
Console.WriteLine(productsSqlDataReader[0]);
Console.WriteLine(productsSqlDataReader[1]);
Console.WriteLine(productsSqlDataReader[2]);
Console.WriteLine(productsSqlDataReader[3]);
}

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.

Chương trình 5.1: Đọc dữ liệu từ cơ sở dữ liệu dùng DataReader


/*
UsingColumnOrdinals.cs illustrates how to use the GetOrdinal()
method of a DataReader object to get the numeric positions of
a column
*/

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
{

public static void Main()


{

SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice, " +
"UnitsInStock, Discontinued " +
"FROM Products " +
"ORDER BY ProductID";

mySqlConnection.Open();

SqlDataReader productsSqlDataReader =
mySqlCommand.ExecuteReader();

// use the GetOrdinal() method of the DataReader object


// to get the numeric positions of the columns
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");

// Read column values


while (productsSqlDataReader.Read())
{
Console.WriteLine("ProductID = " +
productsSqlDataReader[productIDColPos]);

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]);
}

// Close DataReader and Connection


productsSqlDataReader.Close();
mySqlConnection.Close();
}
}

Kết quả khi chạy chương trình


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

5.4. Lấy giá trị từ các cột với kiểu cụ thể


Cho đến thời điểm này, giá trị của các cột lấy được từ DataReader đều có kiểu
System.Object. Mọi lớp trong C# đều được dẫn xuất từ lớp này.
Ví dụ sau minh họa cách lưu giá trị của các cột theo kiểu System.Object.
while (productsSqlDataReader.Read())
{
object productID =
productsSqlDataReader[productIDColPos];
object productName =
productsSqlDataReader[productNameColPos];
object unitPrice =
productsSqlDataReader[unitPriceColPos];

Trang 170
Giáo trình lập trình cơ sở dữ liệu

object unitsInStock =
productsSqlDataReader[unitsInStockColPos];
object discontinued =
productsSqlDataReader[discontinuedColPos];

Console.WriteLine("productID = " + productID);


Console.WriteLine("productName = " + productName);
Console.WriteLine("unitPrice = " + unitPrice);
Console.WriteLine("unitsInStock = " + unitsInStock);
Console.WriteLine("discontinued = " + discontinued);
}

Đ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);

5.5. Sử dụng các phương thức dạng Get* để đọc dữ liệu


Trước khi tìm hiểu cách sử dụng các phương thức Get* của đối tượng DataReader
để đọc dữ liệu, ta cần khảo sát các kiểu dữ liệu chuẩn của C# và kiểu dữ liệu tương ứng
của chúng trong SQL Server.

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

SQL SERVER TYPE COMPATIBLE STANDARD C# TYPE GET* METHOD


binary byte[] GetBytes()
bigint long GetInt64()
bit bool GetBoolean()
char string GetString()
datetime DateTime GetDateTime()
decimal decimal GetDecimal()
float double GetDouble()
image byte[] GetBytes()

Trang 172
Giáo trình lập trình cơ sở dữ liệu

SQL SERVER TYPE COMPATIBLE STANDARD C# TYPE GET* METHOD


int int GetInt32()
money decimal GetDecimal()
nchar string GetString()
ntext string GetString()
nvarchar string GetString()
numeric decimal GetDecimal()
real float GetFloat()
smalldatetime DateTime GetDateTime()
smallint short GetInt16()
smallmoney decimal GetDecimal()
sql_varient object GetValue()
text string GetString()
timestamp byte[] GetBytes()
tinyint byte GetByte()
varbinary byte[] GetBytes()
varchar string GetString()
uniqueidentifier Guid GetGuid()
Bảng 5.4.SQL Server Types, Standard C# Types and Get* Methods

Để 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.

5.5.1. Lấy kiểu dữ liệu của một cột


Đối tượng DataReader cung cấp hai phương thức GetFieldType và
GetDataTypeName cho phép lấy tên kiểu dữ liệu chuẩn C# và tên kiểu dữ liệu SQL
Server của một cột.
// Lấy kiểu dữ liệu chuẩn C#
Console.WriteLine("ProductID .NET type = " +
productsSqlDataReader.GetFieldType(productIDColPos));

// Kết quả trả về


ProductID .NET type = System.Int32

// Lấy kiểu dữ liệu CSDL SQL Server


Console.WriteLine("ProductID database type = " +
productsSqlDataReader.GetDataTypeName(productIDColPos));

// Kết quả trả về


ProductID database type = int

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
{

public static void Main()


{

SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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");

// use the GetFieldType() method of the DataReader object


// to obtain the .NET type of a column

Console.WriteLine("ProductID .NET type = " +


productsSqlDataReader.GetFieldType(productIDColPos));

Console.WriteLine("ProductName .NET type = " +


productsSqlDataReader.GetFieldType(productNameColPos));

Console.WriteLine("UnitPrice .NET type = " +


productsSqlDataReader.GetFieldType(unitPriceColPos));

Console.WriteLine("UnitsInStock .NET type = " +


productsSqlDataReader.GetFieldType(unitsInStockColPos));

Trang 175
Giáo trình lập trình cơ sở dữ liệu

Console.WriteLine("Discontinued .NET type = " +


productsSqlDataReader.GetFieldType(discontinuedColPos));

// use the GetDataTypeName() method of the DataReader object


// to obtain the database type of a column

Console.WriteLine("ProductID database type = " +


productsSqlDataReader.GetDataTypeName(productIDColPos));

Console.WriteLine("ProductName database type = " +


productsSqlDataReader.GetDataTypeName(productNameColPos));

Console.WriteLine("UnitPrice database type = " +


productsSqlDataReader.GetDataTypeName(unitPriceColPos));

Console.WriteLine("UnitsInStock database type = " +


productsSqlDataReader.GetDataTypeName(unitsInStockColPos));

Console.WriteLine("Discontinued database type = " +


productsSqlDataReader.GetDataTypeName(discontinuedColPos));

// read the column values using Get* methods that


// return specific C# types
while (productsSqlDataReader.Read())
{
int productID =
productsSqlDataReader.GetInt32(productIDColPos);
Console.WriteLine("productID = " + productID);

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();

}
}

Kết quả khi chạy chương trình


ProductID .NET type = System.Int32
ProductName .NET type = System.String
UnitPrice .NET type = System.Decimal
UnitsInStock .NET type = System.Int16
Discontinued .NET type = System.Boolean

Trang 176
Giáo trình lập trình cơ sở dữ liệu

ProductID database type = int


ProductName database type = nvarchar
UnitPrice database type = money
UnitsInStock database type = smallint
Discontinued database type = bit

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

5.5.2. Đọc dữ liệu bằng phương thức GetSql*


Ngoài cách dùng các phương thức dạng Get* để lấy giá trị các cột theo kiểu dữ liệu
chuẩn của C#, ta có thể dùng các phương thức dạng GetSql*. Giá trị trả về của các
phương thức này có kiểu Sql*, dấu * tương ứng với tên các kiểu dữ liệu của SQL
Server.
Các phương thức dạng GetSql* và kiểu dữ liệu có dạng Sql* được định nghĩa trong
namespace System.Data.SqlTypes. Chúng chỉ được dùng cho cơ sở dữ liệu SQL Server.
Ngoài ra, các phương thức dạng GetSql* chỉ có trong lớp SqlDataReader. Việc sử dụng
các phương thức GetSql* và kiểu dữ liệu Sql* ngăn chặn các lỗi do chuyển đổi kiểu và
tránh được tình trạng sai số.
Các phương thức GetSql* cũng thực thi nhanh hơn các phương thức Get* tương
ứng. Sở dĩ có điều này là vì các phương thức GetSql không cần phải chuyển đổi kiểu dữ
liệu SQL Server sang kiểu dữ liệu chuẩn của C#.

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*

SQL SERVER TYPE Sql* TYPE GetSql* METHOD


bigint SqlInt64 GetSqlInt64()
int SqlInt32 GetSqlInt32()
smallint SqlInt16 GetSqlInt16()
tinyint SqlByte GetSqlByte()
bit SqlBoolean GetSqlBoolean()
decimal SqlDecimal GetSqlDecimal()
numeric SqlDecimal GetSqlDecimal()
money SqlMoney GetSqlMoney()
smallmoney SqlMoney GetSqlMoney()
float SqlDouble GetSqlDouble()
real SqlSingle GetSqlSingle()
datetime SqlDateTime GetSqlDateTime()
smalldatetime SqlDateTime GetSqlDateTime()
char SqlString GetSqlString()
varchar SqlString GetSqlString()
text SqlString GetSqlString()
nchar SqlString GetSqlString()
nvarchar SqlString GetSqlString()
ntext SqlString GetSqlString()
binary SqlBinary GetSqlBinary()
varbinary SqlBinary GetSqlBinary()

Trang 178
Giáo trình lập trình cơ sở dữ liệu

SQL SERVER TYPE Sql* TYPE GetSql* METHOD


image SqlBinary GetSqlBinary()
sql_varient object GetSqlValue()
timestamp SqlBinary GetSqlBinary()
uniqueidentifier SqlGuid GetSqlGuid()
Bảng 5.7. SQL Server Types, Sql* Types and GetSql* Methods

Để 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
{

public static void Main()


{

SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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");

// read the column values using GetSql* methods that


// return specific Sql* types

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();
}
}

Kết quả khi chạy chương trình


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 181
Giáo trình lập trình cơ sở dữ liệu

5.6. Xử lý giá trị null


Như đã đề cập trong chương 1, một bảng trong cơ sở dữ liệu có thể chứa các cột cho
phép nhận giá tri null. Giá trị null cho biết giá trị của cột đó là chưa biết. Các kiểu dữ
liệu chuẩn của C# không thể biểu diễn (hay lưu trữ) giá trị null này. Vì thế, ta phải dùng
kiểu Sql*.
Nếu bạn gán giá trị null cho một biến được khai báo với kiểu dữ liệu chuẩn của C#
(kể cả kiểu System.Object), lỗi System.Data.SqlTypes.SqlNullValueException sẽ phát
sinh. Ví dụ:
decimal unitPrice =
productsSqlDataReader.GetDecimal(unitPriceColPos);

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);

Kết quả xuất: unitPrice = Null

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

Kết quả xuất: unitPrice.IsNull = true


Cho biết biến unitPrice đang lưu giữ giá trị null.

5.7. Thực thi nhiều lệnh truy vấn SQL


Thông thường, chương trình ứng dụng và cơ sở dữ liệu nằm trên hai máy tính khác
nhau và liên lạc với nhau qua mạng. Mỗi khi chương trình thực thi một lệnh truy vấn,
lệnh này được truyền qua mạng đến cơ sở dữ liệu và được cơ sở dữ liệu xử lý. Kết quả
thực thi lại được gửi ngược về cho chương trình thông qua mạng. Như vậy, với những
chương trình ứng dụng tương đối lớn, băng thông mạng sẽ bị chiếm đáng kể. Một cách
hữu ích để giảm lưu lượng các gói tin trên mạng là thực hiện nhiều truy vấn SQL tại
cùng một thời điểm.
Trong phần này, ta sẽ cùng tìm hiểu cách thực thi nhiều lệnh truy vấn SELECT để
lấy dữ liệu và cách để thực hiện nhiều lệnh SELECT, INSERT, UPDATE hay DELETE
xen kẽ nhau.

5.7.1. Thực thi nhiều lệnh SELECT


Xét một ví dụ sử dụng đối tượng Command để thực thi nhiều lệnh SELECT để lấy
kết quả. Đoạn mã sau tạo một đối tượng SqlCommand có tên mySqlCommand và gán 3
lệnh truy vấn SELECT khác nhau cho thuộc tính CommandText.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName " +
"FROM Products " +
"ORDER BY ProductID;" +

"SELECT TOP 3 CustomerID, CompanyName " +


"FROM Customers " +
"ORDER BY CustomerID;" +

"SELECT TOP 6 OrderID, CustomerID " +


"FROM Orders " +
"ORDER BY OrderID;";

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.

SqlDataReader productsSqlDataReader = mySqlCommand.ExecuteReader();

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.

Chương trình 5.4: Thực thi nhiều lệnh SELECT


/*
ExecuteMultipleSelects.cs illustrates how to execute
multiple SELECT statements using a SqlCommand object
and read the results using a SqlDataReader object
*/
using System;
using System.Data;
using System.Data.SqlClient;

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// set the CommandText property of the SqlCommand object to


// the mutliple SELECT statements
mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName " +
"FROM Products " +
"ORDER BY ProductID;" +

"SELECT TOP 3 CustomerID, CompanyName " +


"FROM Customers " +

"ORDER BY CustomerID;" +
"SELECT TOP 6 OrderID, CustomerID " +
"FROM Orders " +
"ORDER BY OrderID;";

mySqlConnection.Open();

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();

// read the result sets from the SqlDataReader object using


// the Read() and NextResult() methods
do
{
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[0] = " +
mySqlDataReader[0]);

Console.WriteLine("mySqlDataReader[1] = " +
mySqlDataReader[1]);
}
Console.WriteLine(""); // visually split the results

} while (mySqlDataReader.NextResult());

mySqlDataReader.Close();
mySqlConnection.Close();

}
}

Kết quả khi chạy chương trình


mySqlDataReader[0] = 1
mySqlDataReader[1] = Chai
mySqlDataReader[0] = 2
mySqlDataReader[1] = Chang

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

5.7.2. Thực thi nhiều lệnh SELECT, INSERT, UPDATE và DELETE


Ta cũng có thể xen kẽ nhiều lệnh SELECT, INSERT, UPDATE và DELETE với
nhau. Điều này giúp tiết kiệm được băng thông mạng vì nhiều lệnh chỉ được gửi một lần
tới cơ sở dữ liệu.
Đoạn mã sau tạo một đối tượng SqlCommand tên là mySqlCommand và gán nhiều
lệnh SQL xen kẽ nhau cho thuộc tính CommandText của nó.

mySqlCommand.CommandText =
"INSERT INTO Customers (CustomerID, CompanyName) " +
"VALUES ('J5COM', 'Jason 5 Company');" +

"SELECT CustomerID, CompanyName " +


"FROM Customers " +
"WHERE CustomerID = 'J5COM';" +

"UPDATE Customers " +


"SET CompanyName = 'Another Jason Company' " +
"WHERE CustomerID = 'J5COM';" +

"SELECT CustomerID, CompanyName " +


"FROM Customers " +
"WHERE CustomerID = 'J5COM';" +

"DELETE FROM Customers " +


"WHERE CustomerID = 'J5COM';";

Ý nghĩa của các lệnh SQL như sau:


Trang 186
Giáo trình lập trình cơ sở dữ liệu

 Lệnh INSERT để thêm một dòng mới vào bảng Customers


 Lệnh SELECT thứ nhất lấy dữ liệu từ dòng mới thêm
 Lệnh UPDATE cập nhật giá trị cho cột CompanyName của dòng mới
 Lệnh SELECT thứ hai lấy dữ liệu từ dòng vừa được cập nhật
 Lệnh DELETE để xóa dòng đã thêm vào

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.

Chương trình 5.5: Thực thi nhiều lệnh SQL xen kẽ


/*
ExecuteMultipleSQL.cs illustrates how to execute
multiple SQL statements using a SqlCommand object
*/
using System;
using System.Data;
using System.Data.SqlClient;

class ExecuteMultipleSQL
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// set the CommandText property of the SqlCommand object to


// the INSERT, UPDATE, and DELETE statements

mySqlCommand.CommandText =
"INSERT INTO Customers (CustomerID, CompanyName) " +
"VALUES ('J5COM', 'Jason 5 Company');" +

"SELECT CustomerID, CompanyName " +


"FROM Customers " +
"WHERE CustomerID = 'J5COM';" +

"UPDATE Customers " +


"SET CompanyName = 'Another Jason Company' " +
"WHERE CustomerID = 'J5COM';" +

"SELECT CustomerID, CompanyName " +


"FROM Customers " +
"WHERE CustomerID = 'J5COM';" +

Trang 187
Giáo trình lập trình cơ sở dữ liệu

"DELETE FROM Customers " +


"WHERE CustomerID = 'J5COM';";

mySqlConnection.Open();

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();

// read the result sets from the SqlDataReader object using


// the Read() and NextResult() methods
do
{
while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[0] = " +
mySqlDataReader[0]);
Console.WriteLine("mySqlDataReader[1] = " +
mySqlDataReader[1]);
}
Console.WriteLine(""); // visually split the results

} while (mySqlDataReader.NextResult());

mySqlDataReader.Close();
mySqlConnection.Close();

}
}

Kết quả chạy chương trình


mySqlDataReader[0] = J5COM
mySqlDataReader[1] = Jason 5 Company

mySqlDataReader[0] = J5COM
mySqlDataReader[1] = Another Jason Company

5.8. Sử dụng DataReader trong Visual Studio .NET


Trong VS.Net, bạn không thể tạo đối tượng DataReader bằng cách kéo thả như
SqlCommand hay SqlConnection. Thay vào đó, ta tạo DataReader bằng cách viết mã
lệnh.
Trong phần này, ta sẽ tạo một đối tượng SqlDataReader và sử dụng nó để lấy kết quả
từ đối tượng SqlCommand. Đối tượng SqlCommand này chứa một lệnh SELECT để lấy
dữ liệu trên các cột CustomerID, CompanyName và ContactName của bảng Customers.
Đối tượng SqlDataReader lấy kết quả truy vấn từ lệnh SELECT và hiển thị lên một điều
khiển ListView. ListView là một điều khiển cho phép hiển thị thông tin ở dạng lưới, có
cả các dòng và các cột.
Để làm được điều này, thực hiện theo các bước sau:
- Tạo đối tượng SqlConnection và SqlCommand bằng cách kéo chúng từ thể Data
của Toolbox vào Form. Hai đối tượng này sẽ có tên mặc định là sqlConnection1

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.

Hình 5.1. Tạo một ListView trong VS.Net

- 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.2. Tạo các cột cho ListView


- Lần lượt thay đổi tiêu đề (Text), độ rộng (Width) của các cột như hình 5.3

Hình 5.3. Thiết lập thuộc tính cho các cột của ListView

- Nhấp đôi chuột lên Form để mở cửa sổ viết mã lệnh


- Trong phương thức xử lý sự kiện Form_Load, thêm đoạn mã sau

Trang 190
Giáo trình lập trình cơ sở dữ liệu

- Chạy chương trình để xem kết quả

Hình 5.4. Kết quả chạy chương trình

Trang 191
Giáo trình lập trình cơ sở dữ liệu

5.9. Kết chương

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.

Bài tập chương 5

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.

Những nội dung chính


 Lớp SqlDataAdapter
 Tạo một đối tượng SqlDataAdapter
 Lớp DataSet
 Tạo một đối tượng DataSet
 Đưa dữ liệu vào DataSet
 Trộn nhiều DataSet, DataTable
 Ánh xạ các cột và các bảng
 Tạo và sử dụng các DataSet có định kiểu
 Tạo đối tượng DataAdapter và DataSet dùng VS.Net

6.1. Lớp SqlDataAdapter


Lớp SqlDataAdapter đóng vai trò cầu nối giữa nhóm lớp kết nối và không kết nối.
Nó được dùng để đồng bộ hóa dữ liệu được lưu trong một DataSet với cơ sở dữ liệu
SQL Server.
Tương tự, đối tượng OleDbDataAdapter (OdbcDataAdapter) được dùng để sao chép
các dòng từ cơ sở dữ liệu có hỗ trợ OLE DB (ODBC) như Oracle hay Access vào một
DataSet và cập nhật các thay đổi trong DataSet ngược trở lại 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.

Khi được gán là true, các lỗi sẽ được bỏ


qua trong quá trình cập nhật một dòng.
Nếu xảy ra lỗi, việc cập nhật bị hủy bỏ
và thông tin về lỗi đó được đưa vào thuộc
tính RowError của dòng (DataRow) gây ra
lỗi. DataAdapter tiếp tục cập nhật các
dòng còn lại.

Khi được gán là false, quá trình cập nhật


bị ngắt khi có lỗi xảy ra.
DeleteCommand SqlCommand Thuộc tính đọc - ghi. Lấy hay gán lệnh
SQL DELETE hoặc tên thủ tục dùng để xóa
các dòng khỏi một bảng của cơ sở dữ liệu.
InsertCommand SqlCommand Thuộc tính đọc - ghi. Lấy hay gán lệnh
SQL INSERT hoặc tên thủ tục dùng để thêm
một dòng vào bảng trong cơ sở dữ liệu.
MissingMappingAction MissingMa- Thuộc tính đọc - ghi. Lấy hay gán dạng
ppingActio thao tác xử lý khi có một cột hay bảng
n nhận về không phù hợp với bảng ánh xạ cấu
trúc cơ sở dữ liệu (TableMappings).

Các giá trị khả dĩ cho thuộc tính này


được lấy từ kiểu enum
System.Data.MissingMappingAction (Error,
Ignore và Passthrough):

 Error nghĩa là sẽ phát sinh ra lỗi.


 Ignore nghĩa là cột hay bảng sẽ bị
loại bỏ và không được đọc.
 Passthrough nghĩa là bảng hay cột
đó vẩn được thêm vào DataSet với
tên ban đầu.

Giá trị mặc định là Passthrough.


MissingSchemaAction MissingSc- Thuộc tính đọc - ghi. Lấy hay gán dạng
hemaAction thao tác xử lý khi có một cột nhận về
không phù hợp với cột được định nghĩa
trong thuộc tính Columns của DataTable.

Các giá trị cho thuộc tính này lấy từ


kiểu enum System.Data.MissingSchemaAction

Trang 194
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


(Add, AddWithKey, Error và Ignore):

 Add cho biết cột đó vẫn được thêm


vào DataTable.
 AddWithKey nghĩa là cột đó và thông
tin khóa chính được thêm vào
DataTable.
 Error cho biết sẽ phát sinh ra lỗi.
 Ignore cho biết cột đó sẽ bị loại
bỏ và không được đọc.

Giá trị mặc định là Add.


SelectCommand SqlCommand Thuộc tính đọc - ghi. Lấy hay gán lệnh
SQL SELECT hoặc tên thủ tục để lấy các
dòng từ cơ sở dữ liệu.
TableMappings DataTable- Thuộc tính chỉ đọc. Lấy về một đối tượng
MappingCo- DataTableMappingCollection chứa ánh xạ
llection giữa một bảng trong cơ sở dữ liệu với một
đối tượng DataTable trong DataSet.
UpdateCommand SqlCommand Thuộc tính đọc - ghi. Lấy hay gán lệnh
SQL UPDATE hoặc tên thủ tục để cập nhật
dữ liệu trong các dòng của một bảng.
Bảng 6.1. Các thuộc tính của SqlDataAdapter

METHOD RETURN TYPE DESCRIPTION


Fill() int Phương thức có nhiều dạng. Lấy các dòng
từ cơ sở dữ liệu và đưa vào DataSet. Trả
về số nguyên cho biết số dòng được lấy
về.
FillSchema() DataTable Phương thức có nhiều dạng. Thêm một hoặc
DataTable[] nhiều DataTable vào DataSet và cấu hình
cho lược đồ khớp với cơ sở dữ liệu.
GetFillParameters() IDataParameter[] Trả về danh sách các tham số sử dụng
trong lệnh SQL SELECT.
Update() int Phương thức có nhiều dạng. Gọi các lệnh
SQL INSERT UPDATE, DELETE hay thủ tục
(được lưu trong các thuộc tính
InsertCommand, UpdateCommand, and
DeleteCommand tương ứng) trên các dòng
được thêm vào, bị thay đổi hay bị xóa
khỏi DataTable. properties,
respectively). Trả về số nguyên cho biết
số dòng bị thay đổi.
Bảng 6.2. Các phương thức của SqlDataAdapter

EVENT EVENT HANDLER DESCRIPTION


FillError FillErrorEventHandler Phát sinh khi có lỗi xảy ra trong hàm
Fill.
RowUpdating RowUpdatingEventHandler Phát sinh trước khi một dòng được thêm
vào, bị thay đổi hay xóa khỏi cơ sở dữ
liệu.
RowUpdated RowUpdatedEventHandler Phát sinh sau khi một dòng được thêm vào,

Trang 195
Giáo trình lập trình cơ sở dữ liệu

EVENT EVENT HANDLER DESCRIPTION


bị thay đổi hay xóa khỏi cơ sở dữ liệu.
Bảng 6.3. Các sự kiện của SqlDataAdapter

6.2. Tạo đối tượng SqlDataAdapter


Để tạo một đối tượng SqlDataAdapter, ta sử dụng một trong các phương thức khởi
tạo của lớp SqlDataAdapter như sau:
SqlDataAdapter ()
SqlDataAdapter (SqlCommand mySqlCommand)
SqlDataAdapter (string selectCommandString, SqlConnection mySqlConn)
SqlDataAdapter (string selectCommandString, string connectionString)
Trong đó
 mySqlCommand: là đối được thực thi lệnh SqlCommand
 selectCommandString: là lệnh SQL SELECT hoặc tên một thủ tục.
 mySqlConn: là đối tượng kết nối SqlConnection
 connectionString: là chuỗi chứa thông tin kết nối 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.

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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.

6.3. Lớp DataSet


Đối tượng DataSet dùng để biểu diễn một bản sao thông tin được lưu trong cơ sở dữ
liệu. Ta có thể tạo ra các thay đổi về mặt dữ liệu trên bản sao này mà không làm ảnh
hưởng đến cơ sở dữ liệu đang tồn tại và sau đó đồng bộ hóa các thay đổi với cơ sở dữ
liệu qua đối tượng DataAdapter. Một DataSet có thể biểu diễn các cấu trúc của cơ sở dữ
liệu như bảng (Table), các hàng (Row) và các cột. Thậm chí là cả các ràng buộc hay
quan hệ giữa các bảng. Chẳng hạn như ràng buộc khóa ngoại, tính duy nhất của dữ
liệu…
Hình 6.1 cho thấy đối tượng DataSet và mối quan hệ của nó đến một vài đối tượng
khác (trong nhóm các lớp không kết nối) mà nó lưu trữ. Theo hình này, một DataSet có
thể lưu trữ nhiều DataTable, nhiều mối quan hệ…
Các bảng dưới đây liệt kê danh sách các thuộc tính, phương thức, sự kiện của lớp
DataSet và ý nghĩa của chúng.

Trang 197
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


CaseSensitive bool Thuộc tính đọc và ghi, cho biết việc so sánh
chuỗi trong các đối tượng DataTable có phân
biệt chữ hoa-thường hay không.
DataSetName string Thuộc tính đọc và ghi. Lấy hay gán tên của
DataSet hiện tại.
DefaultViewManager DataView- Thuộc tính chỉ đọc. Lấy giá trị của khung
Manager nhìn (custom view) dữ liệu được lưu trong
DataSet. Bạn sử dụng khung nhìn (view) để
trích lọc, tìm kiếm, sắp xếp và điều hướng
trong DataSet.
EnforceConstraints bool Thuộc tính đọc và ghi. Cho biết các quy luật
ràng buộc dữ liệu có được áp dụng khi cập
nhật thông tin trong DataSet hay không.
ExtendedProperties Property- Thuộc tính chỉ đọc. Lấy tập (Property-
Collection Collection) các thông tin người dùng. Bạn có
thể dùng PropertyCollection để lưu trữ các
chuỗi biểu diễn bất cứ thông tin bổ sung nào
mà bạn muốn. Gọi phương thức Add() của thuộc
tính ExtendedProperties để thêm một chuỗi.
HasErrors bool Thuộc tính chỉ đọc. Cho biết có lỗi xảy ra
sau khi thực hiện một thao tác trên các dòng
nào đó của bảng trong DataSet hay không.
Locale CultureInfo Thuộc tính đọc và ghi. Lấy hay gán một đối
tượng CultureInfo cho DataSet. Một đối tượng
CultureInfo chứa các thông tin về một nền
văn hóa cụ thể, bao gồm tên, hệ thống chữ
viết và lịch.
Namespace string Thuộc tính đọc và ghi. Lấy hay gán namespace
cho đối tượng DataSet. Namespace là một
chuỗi được dùng khi đọc và ghi một tài liệu
XML dùng các phương thức ReadXml(),
WriteXml(), ReadXmlSchema(), and
WriteXmlSchema(). The namespace được dùng để
xác định phạm vi của các thuộc tính và các
phần tử XML.
Prefix string Thuộc tính đọc và ghi. Lấy hay gán tiền tố
XML (XML prefix) namespace của DataSet. Tiền
tố này được dùng trong tài liệu XML để xác
định các phần tử nào thuộc namespace của đối
tượng DataSet.
Relations DataRelation- Thuộc tính chỉ đọc. Lấy tập các quan hệ
Collection (DataRelationCollection) từ một bảng cha đến
một bảng con. Một DataRelationCollection gồm
nhiều đối tượng DataRelation.
Tables DataTable- Thuộc tính chỉ đọc. Lấy danh sách các bảng
Collection (DataTableCollection) có trong DataSet. Một
DataTableCollection chứa nhiều DataTable.
Bảng 6.4. Các thuộc tính của DataSet

METHOD RETURN DESCRIPTION


TYPE
AcceptChanges() void Xác nhận (Commits) tất cả các thay đổi được tạo
ra trong DataSet khi nó được tải hoặc khi phương

Trang 198
Giáo trình lập trình cơ sở dữ liệu

METHOD RETURN DESCRIPTION


TYPE
thức AcceptChanges() cuối cùng được gọi.
BeginInit() void Visual Studio .NET sử dụng phương thức này để
khởi tạo một DataSet trên form hoặc component
khi ở chế độ thiết kết (Design Mode).
Clear() void Xóa tất cả các dòng trong mọi bảng của đối tượng
DataSet.
Clone() DataSet Tạo một bản sao cấu trúc của đối tượng DataSet
và trả về bản sao đó. Nội dung bản sao chứa lược
đồ, quan hệ và các các ràng buộc.
Copy() DataSet Tạo một bản sao cấu trúc và dữ liệu trong
DataSet và trả về bản sao đó. Nội dung bản sao
chứa lược đồ, quan hệ, các ràng buộc và cả dữ
liệu.
EndInit() void Visual Studio .NET designer để kee6t1 thúc việc
khởi tạo đối tượng DataSet trên form hay
component khi đang ở chế độ thiết kế.
GetChanges() DataSet Có nhiều dạng. Lấy một bản sao tất cả các thay
đổi xảy ra trong đối tượng DataSet khi nó được
tải xong hoặc khi phương thức AcceptChanges()
được gọi lần sau cùng.
GetXml() string Trả về dữ liệu được lưu trong đối tượng DataSet
ở định dạng XML.
GetXmlSchema() string Trả về lược đồ (cấu trúc) của đối tượng DataSet
ở định dạng XML.
HasChanges() bool Có nhiều dạng. Trả về giá trị true/ false cho
biết DataSet có thay đổi nào chưa được xác nhận
hay không.
Merge() void Có nhiều dạng. Trộn DataSet này với một đối
tượng DataSet khác.
ReadXml() XmlReadMode Có nhiều dạng. Tải dữ liệu từ tập tin XML vào
đối tượng DataSet.
ReadXmlSchema() void Có nhiều dạng. Tải lược đồ XML (XML Schema) vào
đối tượng DataSet.
RejectChanges() void Hủy bỏ mọi thay đổi xảy ra trong đối tượng
DataSet từ khi nó được tạo hoặc từ lúc phương
thức AcceptChanges() được gọi lần cuối.
Reset() void Đặt lại đối tượng DataSet về trạng thái ban đầu.
WriteXml() void Có nhiều dạng. Ghi dữ liệu từ đối tượng DataSet
ra một tập tin XML.
WriteXmlSchema() void Có nhiều dạng. Ghi lược đồ (cấu trúc) DataSet ra
một tập tin XML.
Bảng 6.5. Các phương thức của DataSet.

EVENT EVENT HANDLER DESCRIPTION


MergeFailed MergeFailedEventHandler Phát sinh khi cố gắng tạo một dòng
(DataRow) mới để thêm vào DataSet và dòng
này chứa giá trị trên cột khóa chính trùng
với một giá trị đang tồn tại trong
DataSet.
Bảng 6.6. Các sự kiện của DataSet.

Trang 199
Giáo trình lập trình cơ sở dữ liệu

6.4. Tạo một đối tượng DataSet


Để tạo một đối tượng DataSet, ta dùng một trong hai phương thức khởi tạo sau
DataSet()
DataSet(string dataSetNameString)

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ớ.

Hình 6.1. Các lớp không kết nối


Đoạn mã sau minh họa cách sử dụng hai phương thức tạo lập ở trên để tạo đối tượng
DataSet.
// Tạo một đối tượng DataSet mới

Trang 200
Giáo trình lập trình cơ sở dữ liệu

DataSet myDataSet = new DataSet();

// Tạo một đối tượng DataSet mới


// có tên là myDataSet
DataSet myDataSet = new DataSet("myDataSet");

6.4.1. Đưa dữ liệu vào DataSet


Phần này trình bày cách dùng phương thức Fill() của đối tượng DataAdapter để đưa
dữ liệu trả về bởi lệnh SELECT, thủ tục hay một phần của tập dữ liệu đó vào DataSet.
Hàm này trả về số nguyên chỉ ra số dòng được đưa vào DataSet bởi đối tượng
DataAdapter.

Cách 1: Dùng lệnh SELECT


Trước khi lấy được dữ liệu vào đưa vào DataSet, ta cần phải tạo các đối tượng
Connection, Command và DataAdapter.
// Tạo đối tượng kết nối cơ sở dữ liệu
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

// Tạo đối tượng thực thi lệnh


SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// Thiết lập lệnh truy vấn cho đối tượng Command


mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";

// Tạo đối tượng DataAdapter


SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();

// Thiết lập đối tượng Command cho DataAdapter


mySqlDataAdapter.SelectCommand = mySqlCommand;

// Tạo đối tượng DataSet


DataSet myDataSet = new DataSet();

// Mở kết nối tới CSDL


mySqlConnection.Open();

Đ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

int numberOfRows = mySqlDataAdapter.Fill(myDataSet, "Products");

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"
);

// create a SqlCommand object and set its CommandText property


// to a SELECT statement that retrieves the top 5 rows from
// the Products table

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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

// create a SqlDataAdapter object and set its SelectCommand


// property to the SqlCommand object

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

// create a DataSet object

DataSet myDataSet = new DataSet();

// open the database connection

mySqlConnection.Open();

// use the Fill() method of the SqlDataAdapter object to


// retrieve the rows from the table, storing the rows locally
// in a DataTable of the DataSet object

Console.WriteLine("Retrieving rows from the Products table");

int numberOfRows = mySqlDataAdapter.Fill(myDataSet, "Products");

Console.WriteLine("numberOfRows = " + numberOfRows);

// close the database connection

mySqlConnection.Close();

// get the DataTable object from the DataSet object

DataTable myDataTable = myDataSet.Tables["Products"];

// display the column values for each row in the DataTable,


// using a DataRow object to access each row in the DataTable

foreach (DataRow myDataRow in myDataTable.Rows)


{
Console.WriteLine("ProductID = " +
myDataRow["ProductID"]);

Console.WriteLine("ProductName = " +
myDataRow["ProductName"]);

Console.WriteLine("UnitPrice = " +
myDataRow["UnitPrice"]);
}
}
}

Kết quả chạy chương trình


Retrieving rows from the Products table
numberOfRows = 5
ProductID = 1
ProductName = Chai
UnitPrice = 18
ProductID = 2
ProductName = Chang
UnitPrice = 19

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

Cách 2: Lấy một phần của tập kết quả


Trong phần trước, ta đã xét một ví dụ lấy về tất cả các dòng từ kết quả truy vấn bằng
cách dùng phương thức Fill() của đối tượng DataAdapter.
Tiếp theo, ta sẽ tìm hiểu các dạng khác của phương thức Fill() để lấy một phần của
tập kết quả sau khi truy vấn.
int Fill(DataSet myDataSet)
int Fill(DataTable myDataTable)
int Fill(DataSet myDataSet, string dataTableName)
int Fill(DataSet myDataSet, int startRow, int numOfRows,
string dataTableName)
Trong đó:
 myDataSet: là đối tượng DataSet chứa kết quả trả về sau truy vấn.
 myDataTable: là đối tượng DataTable chứa kết quả trả về sau truy vấn.
 dataTableName: là chuỗi chứa tên của DataTable sẽ chứa dữ liệu kết quả.
 startRow: chỉ số của dòng bắt đầu lấy dữ liệu trong tập dữ kết quả
 numOfRows: số dòng muốn lấy từ tập kết quả

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";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();

mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();

int numberOfRows = mySqlDataAdapter.Fill(myDataSet, 1, 3, "Products");

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"
);

// create a SqlCommand object and set its CommandText property


// to a SELECT statement that retrieves the top 5 rows from
// the Products table

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();

mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

// use the Fill() method of the SqlDataAdapter object to


// retrieve the rows from the table, storing a range of rows
// in a DataTable of the DataSet object

Console.WriteLine("Retrieving rows from the Products table");

int numberOfRows =
mySqlDataAdapter.Fill(myDataSet, 1, 3, "Products");

Console.WriteLine("numberOfRows = " + numberOfRows);

mySqlConnection.Close();

Trang 206
Giáo trình lập trình cơ sở dữ liệu

DataTable myDataTable = myDataSet.Tables["Products"];

foreach (DataRow myDataRow in myDataTable.Rows)


{
Console.WriteLine("ProductID = " +
myDataRow["ProductID"]);

Console.WriteLine("ProductName = " +
myDataRow["ProductName"]);

Console.WriteLine("UnitPrice = " +
myDataRow["UnitPrice"]);
}
}
}

Kết quả chạy chương trình


Retrieving rows from the Products table
numberOfRows = 3

ProductID = 2
ProductName = Chang
UnitPrice = 19

ProductID = 3
ProductName = Aniseed Syrup
UnitPrice = 10

ProductID = 4
ProductName = Chef Anton's Cajun Seasoning
UnitPrice = 22

Cách 3: Sử dụng Stored Procedure


Ta cũng có thể đưa dữ liệu vào DataSet từ kết quả trả về sau khi thực thi một lời gọi
thủ tục (Stored Procedure).
Chẳng hạn, trong cơ sở dữ liệu Northwind của SQL Server, có một thủ tục
CustOrderHist trả về các sản phẩm và tổng số sản phẩm được đặt hàng bởi một khách
hàng nào đó. Thủ tục này có một tham số là mã khách hàng (CustomerID).
CREATE PROCEDURE CustOrderHist @CustomerID nchar(5)
AS
SELECT ProductName, Total=SUM(Quantity)
FROM Products P, [Order Details] OD, Orders O, Customers C
WHERE C.CustomerID = @CustomerID
AND C.CustomerID = O.CustomerID
AND O.OrderID = OD.OrderID
AND OD.ProductID = P.ProductID
GROUP BY ProductName
Ví dụ:
Đoạn mã sau minh họa cách tạo một đối tượng SqlCommand để thực thi một lời gọi
thủ tục (tên CustOrderHist) được gán qua thuộc tính CommandText để lấy về danh sách
các sản phẩm của một khách hàng nào đó. Tham số mã khách hàng (ở đây, CustomerID

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.

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new 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"
);

// create a SqlCommand object and set its CommandText property


// to call the CustOrderHist() stored procedure

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

Console.WriteLine(
"Retrieving rows from the CustOrderHist() Procedure");

int numberOfRows =
mySqlDataAdapter.Fill(myDataSet, "CustOrderHist");

Console.WriteLine("numberOfRows = " + numberOfRows);

mySqlConnection.Close();
DataTable myDataTable = myDataSet.Tables["CustOrderHist"];

foreach (DataRow myDataRow in myDataTable.Rows)


{
Console.WriteLine("ProductName = " +
myDataRow["ProductName"]);
Console.WriteLine("Total = " + myDataRow["Total"]);
}
}
}

Kết quả chạy chương trình


Retrieving rows from the CustOrderHist() Procedure
numberOfRows = 11

ProductName = Aniseed Syrup


Total = 6
ProductName = Chartreuse verte
Total = 21
ProductName = Escargots de Bourgogne
Total = 40
ProductName = Flotemysost
Total = 20
ProductName = Grandma's Boysenberry Spread
Total = 16
ProductName = Lakkalikööri
Total = 15
ProductName = Original Frankfurter grüne Soße
Total = 2
ProductName = Raclette Courdavault
Total = 15
ProductName = Rössle Sauerkraut
Total = 17
ProductName = Spegesild
Total = 2
ProductName = Vegie-spread
Total = 20

6.4.2. Đưa dữ liệu vào nhiều DataTable của một DataSet


Trong trường hợp muốn truy xuất thông tin được lưu trên nhiều bảng trong cơ sở dữ
liệu, có ba cách để đưa kết quả vào nhiều bảng (DataTable) của một DataSet.
Trang 209
Giáo trình lập trình cơ sở dữ liệu

 Dùng nhiều lệnh SELECT trong thuộc tính SelectCommand


 Thay đổi giá trị cho thuộc tính CommandText của SelectCommand trong
DataAdapter trước mỗi lần gọi hàm Fill
 Sử dụng nhiều đối tượng DataAdapter trên cùng một DataSet

Cách 1: Dùng nhiều lệnh SELECT trong SelectCommand


Đoạn mã sau minh họa cách gán hai lệnh SELECT riêng biệt cho thuộc tính
CommandText của đối tượng SqlCommand. Các lệnh SELECT tách nhau bởi dấu chẩm
phẩy “;”.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

int numberOfRows = mySqlDataAdapter.Fill(myDataSet);


mySqlConnection.Close();

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"
);

// create a SqlCommand object and set its CommandText property


// to mutliple SELECT statements

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID;" +
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI';";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

int numberOfRows = mySqlDataAdapter.Fill(myDataSet);

Console.WriteLine("numberOfRows = " + numberOfRows);


mySqlConnection.Close();

// change the TableName property of the DataTable objects

myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Customers";

foreach (DataTable myDataTable in myDataSet.Tables)


{
Console.WriteLine("\nReading from the " +
myDataTable.TableName + " DataTable");

foreach (DataRow myDataRow in myDataTable.Rows)


{
foreach (DataColumn myDataColumn in
myDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
myDataRow[myDataColumn]);
}
}
}
}

Trang 211
Giáo trình lập trình cơ sở dữ liệu

Kết quả chạy chương trình


numberOfRows = 3

Reading from the Products DataTable


ProductID = 1
ProductName = Chai
UnitPrice = 18
ProductID = 2
ProductName = Chang
UnitPrice = 19

Reading from the Customers DataTable


CustomerID = ALFKI
CompanyName = Alfreds Futterkiste

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";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

int numberOfRows = mySqlDataAdapter.Fill(myDataSet, "Products");

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'";

numberOfRows = mySqlDataAdapter.Fill(myDataSet, "Customers");


mySqlConnection.Close();

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

int numberOfRows = mySqlDataAdapter.Fill(myDataSet, "Products");

Console.WriteLine("numberOfRows = " + numberOfRows);

// change the CommandText property of the SelectCommand

mySqlDataAdapter.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";

numberOfRows = mySqlDataAdapter.Fill(myDataSet, "Customers");

Console.WriteLine("numberOfRows = " + numberOfRows);

mySqlConnection.Close();

foreach (DataTable myDataTable in myDataSet.Tables)

Trang 213
Giáo trình lập trình cơ sở dữ liệu

{
Console.WriteLine("\nReading from the " +
myDataTable.TableName + " DataTable");

foreach (DataRow myDataRow in myDataTable.Rows)


{
foreach (DataColumn myDataColumn in
myDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
myDataRow[myDataColumn]);
}
}
}
}
}

Kết quả khi chạy chương trình


numberOfRows = 2
numberOfRows = 1

Reading from the Products DataTable


ProductID = 1
ProductName = Chai
UnitPrice = 18
ProductID = 2
ProductName = Chang
UnitPrice = 19

Reading from the Customers DataTable


CustomerID = ALFKI
CompanyName = Alfreds Futterkiste

Cách 3: Dùng nhiều DataAdapter để đưa dữ liệu vào một DataSet


Ngoài hai cách trên, ta có thể dùng đưa dữ liệu vào nhiều DataTable của cùng một
DataSet bằng cách dùng nhiều đối tượng DataAdapter khác nhau.
Ví dụ:
Giả sử, ta có một DataSet có tên myDataSet nhận dữ liệu từ một đối tượng
SqlDataAdapter có tên mySqlDataAdapter và DataSet này đang chứa một DataTable có
tên Products.
Tiếp theo, ta tạo một đối tượng SqlDataAdapter mới và dùng nó để đưa dữ liệu vào
cùng một DataSet với một DataTable khác có tên Customers.
SqlDataAdapter mySqlDataAdapter2 = new SqlDataAdapter();
mySqlDataAdapter2.SelectCommand = mySqlCommand;

mySqlDataAdapter2.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";

numberOfRows = mySqlDataAdapter2.Fill(myDataSet, "Customers");

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT TOP 2 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";

SqlDataAdapter mySqlDataAdapter1 = new SqlDataAdapter();


mySqlDataAdapter1.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

int numberOfRows =
mySqlDataAdapter1.Fill(myDataSet, "Products");

Console.WriteLine("numberOfRows = " + numberOfRows);

// create another DataAdapter object

SqlDataAdapter mySqlDataAdapter2 = new SqlDataAdapter();


mySqlDataAdapter2.SelectCommand = mySqlCommand;

mySqlDataAdapter2.SelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";

numberOfRows = mySqlDataAdapter2.Fill(myDataSet, "Customers");

Console.WriteLine("numberOfRows = " + numberOfRows);


mySqlConnection.Close();

foreach (DataTable myDataTable in myDataSet.Tables)

Trang 215
Giáo trình lập trình cơ sở dữ liệu

{
Console.WriteLine("\nReading from the " +
myDataTable.TableName + " DataTable");

foreach (DataRow myDataRow in myDataTable.Rows)


{
foreach (DataColumn myDataColumn in
myDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
myDataRow[myDataColumn]);
}
}
}
}
}

Kết quả khi chạy chương trình


numberOfRows = 2
numberOfRows = 1

Reading from the Products DataTable


ProductID = 1
ProductName = Chai
UnitPrice = 18
ProductID = 2
ProductName = Chang
UnitPrice = 19

Reading from the Customers DataTable


CustomerID = ALFKI
CompanyName = Alfreds Futterkiste

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

void Merge(DataTable myDataTable, bool preserveChanges,


MissingSchemaAction myMissingSchemaAction)

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

// populate myDataSet with three rows from the Customers table


mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"WHERE CustomerID IN ('ALFKI', 'ANATR', 'ANTON')";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();

Trang 217
Giáo trình lập trình cơ sở dữ liệu

mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");

// populate myDataSet2 with two rows from the Customers table

mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, ContactName, Address " +
"FROM Customers " +
"WHERE CustomerID IN ('AROUT', 'BERGS')";

DataSet myDataSet2 = new DataSet();


mySqlDataAdapter.Fill(myDataSet2, "Customers2");

// populate myDataSet3 with five rows from the Products table

mySqlCommand.CommandText =
"SELECT TOP 5 ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";

DataSet myDataSet3 = new DataSet();


mySqlDataAdapter.Fill(myDataSet3, "Products");

mySqlConnection.Close();

// merge myDataSet2 into myDataSet

myDataSet.Merge(myDataSet2);

// merge myDataSet3 into myDataSet

myDataSet.Merge(myDataSet3, true, MissingSchemaAction.Add);

// display the rows in myDataSet


foreach (DataTable myDataTable in myDataSet.Tables)
{
Console.WriteLine("\nReading from the " +
myDataTable + " DataTable");

foreach (DataRow myDataRow in myDataTable.Rows)


{
foreach (DataColumn myDataColumn in
myDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
myDataRow[myDataColumn]);
}
}
}
}
}

Kết quả khi chạy chương trình


Reading from the Customers DataTable
CustomerID = ALFKI
CompanyName = Alfreds Futterkiste
ContactName = Maria Anders

Trang 218
Giáo trình lập trình cơ sở dữ liệu

Address = Obere Str. 57

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

Reading from the Customers2 DataTable


CustomerID = AROUT
CompanyName = Around the Horn
ContactName = Thomas Hardy
Address = 120 Hanover Sq.

CustomerID = BERGS
CompanyName = Berglunds snabbköp
ContactName = Christina Berglund
Address = Berguvsvägen 8

Reading from the Products DataTable


ProductID = 1
ProductName = Chai
UnitPrice = 18

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

6.6. Ánh xạ các bảng và các cột


Ngôn ngữ truy vấn có cấu trúc gọi tắt là SQL (Structured Query Language) cung
cấp từ khóa AS được dùng để đặt tên mới cho một cột hoặc một bảng.
Chẳng hạn, hình sau minh họa một lệnh truy vấn SELECT trên các cột CustomerID,
CompanyName và Address của bảng Customers. Trong đó, cột CustomerID được đổi
tên thành MyCustomer và bảng Customers được đổi tên thành Cust bởi từ khóa AS.

Trang 219
Giáo trình lập trình cơ sở dữ liệu

Hình 6.2. Sử dụng từ khóa AS trong SQL

Đ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.

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT CustomerID AS MyCustomer, CompanyName, Address " +
"FROM Customers AS Cust " +
"WHERE CustomerID = 'ALFKI'";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

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);

Kết quả hiển thị trên màn hình


myDataTableMapping.DataSetTable = Cust
myDataTableMapping.SourceTable = Customers

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.

Chương trình 6.8: Ánh xạ tên bảng và tên cột


/*
Mappings.cs illustrates how to map table and column names
*/

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

mySqlCommand.CommandText =
"SELECT CustomerID AS MyCustomer, CompanyName, Address " +
"FROM Customers AS Cust " +
"WHERE CustomerID = 'ALFKI'";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();

// create a DataTableMapping object

DataTableMapping myDataTableMapping =
mySqlDataAdapter.TableMappings.Add("Customers", "Cust");

// change the TableName property of the DataTable object

myDataSet.Tables["Customers"].TableName = "Cust";

// display the DataSetTable and SourceTable properties


Console.WriteLine("myDataTableMapping.DataSetTable = " +
myDataTableMapping.DataSetTable);

Console.WriteLine("myDataTableMapping.SourceTable = " +
myDataTableMapping.SourceTable);

// map the CustomerID column to MyCustomer


myDataTableMapping.ColumnMappings.Add(
"CustomerID", "MyCustomer");

DataTable myDataTable = myDataSet.Tables["Cust"];

foreach (DataRow myDataRow in myDataTable.Rows)


{
Console.WriteLine("CustomerID = " +
myDataRow["MyCustomer"]);

Console.WriteLine("CompanyName = " +
myDataRow["CompanyName"]);

Console.WriteLine("Address = " + myDataRow["Address"]);


}
}
}

Kết quả khi chạy chương trình


myDataTableMapping.DataSetTable = Cust
myDataTableMapping.SourceTable = Customers

Trang 222
Giáo trình lập trình cơ sở dữ liệu

CustomerID = ALFKI
CompanyName = Alfreds Futterkiste
Address = Obere Str. 57

6.7. DataSet có định kiểu – Strongly Typed DataSet


Một DataSet có định kiểu cho phép đọc giá trị của một cột thông qua một thuộc tính
có tên trùng với tên cột đó. Đây là một tính năng hay vì trình biên dịch có thể bắt các lỗi
xảy ra ngay trong lúc biên dịch thay vì lúc chạy chương trình.
Chẳng hạn, muốn đọc dữ liệu trên cột CustomerID, ta có thể dùng
myDataRow.CustomerID thay vì myDataRow[“ProductID”]. Nếu CustomerID bị viết
sai thành CustimerID, lỗi sẽ được trình biên dịch phát hiện ngay.
Một thuận lợi khác của DataSet có định kiểu là khi làm việc với VS.Net, bộ cảm ứng
thông minh (IntelliSense) tự động mở các phương thức và thuộc tính của DataSet trên
một cửa sổ Popup nhỏ trong lúc ta đang viết chương trình. Bạn có thể chọn các thuộc
tính hay phương thức này từ danh sách được liệt kê sẵn thay vì phải gõ lại tên của
chúng.
Nhược điểm khi sử dụng DataSet có định kiểu là phải tự tạo ra cấu trúc nó trước khi
sử dụng. Nếu các cột trong các bảng của cơ sở dữ liệu không phải thay đổi thì nên xét
đến khả năng sử dụng các đối tượng DataSet có định kiểu. Ngược lại, nếu các bảng
thường có nhiều thay đổi, nên tránh dùng cách này vì cần phải tạo lại cấu trúc và sinh ra
các DataSet để giữ được sự đồng bộ với cấu trúc các bảng trong cơ sở dữ liệu.

6.7.1. Tạo đối tượng DataSet có định kiểu


Phần này trình bày cách tạo một lớp DataSet có định kiểu để lưu trữ thông tin từ
bảng Customers.
- Tạo dự án mới. Trong Form1, tạo một ListView với tên mặc định là listView1.
- Trong cửa sổ Properties của ListView, nhấp chọn nút “…” bên cạnh thuộc tính
Columns để thêm các cột cho ListView.
- Nhấn nút Add để tạo 3 cột, đặt lại tiêu đề cho các cột lần lượt như sau:
CustomerID, CompanyName, Address.
- Chọn menu Project  Add New Item.
- Trong cửa sổ Add New Item, chọn DataSet. Trong ô Name, nhập MyDataSet.
- Nhấn nút Add để thêm DataSet vào dự án

Trang 223
Giáo trình lập trình cơ sở dữ liệu

Hình 6.3. Tạo một DataSet mới

- Mở cửa sổ Server Explorer (View  Server Explorer)


- Tạo một kết nối tới cơ sở dữ liệu Northwind của SQL Server.
- Nhắp chọn Data Connection  chọn kết nối tới cơ sở dữ liệu Northwind.
- Chọn mục Tables để hiển thị danh sách các bảng trong cơ sở dữ liệu Northwind
- Kéo bảng Customers từ Server Explorer vào khung thiết kế DataSet (hình 6.4)
- Một bảng mới có cấu trúc giống như bảng Customers của cơ sở dữ liệu
Northwind với tên Customers được thêm vào khung thiết kế DataSet (hình 6.5)
- Chọn File  Save All hoặc nhất Ctrl + S để lưu tất cả những gì vừa tạo.
- Lúc này, dự án chứa một tập tin XSD có tên MyDataSet.xsd và một tập tin
MyDataSet.cs chứa định nghĩa về DataSet có định kiểu.

6.7.2. Sử dụng DataSet có định kiểu


Một khi đã tạo được một DataSet có định kiểu và lớp MyDataSet, ta có thể tạo một đối
tượng của lớp này như sau:
MyDataSet myDataSet = new MyDataSet();

Trang 224
Giáo trình lập trình cơ sở dữ liệu

Hình 6.4. Khung thiết kế DataSet

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.

private void Form1_Load(object sender, System.EventArgs e)


{
SqlCommand mySqlCommand = sqlConnection1.CreateCommand();

mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Address " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

MyDataSet myDataSet = new MyDataSet();

sqlConnection1.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");
sqlConnection1.Close();

MyDataSet.CustomersDataTable myDataTable = myDataSet.Customers;

Trang 225
Giáo trình lập trình cơ sở dữ liệu

foreach (MyDataSet.CustomersRow myDataRow in myDataTable.Rows)


{
ListViewItem item = listView1.Items.Add(myDataRow.CustomerID);

item.SubItems.Add(myDataRow.CompanyName);
item.SubItems.Add(myDataRow.Address);
}
}

Hình 6.5 Bảng Customers được thêm vào DataSet

Kết quả chạy chương trình

Hình 6.6. Kết quả khi chạy chương trình


Lớp MyDataSet chứa một số phương thức giúp bạn có thể sửa đổi dữ liệu trên các
dòng được lưu trong đối tượng MyDataSet. Các phương thức này gồm
NewCustomersRow(), AddCustomersRow(), FindByCustomerID() và Remove-
CustomersRow(). Bạn cũng có thể kiểm tra giá trị một cột có phải là giá trị null hay
không bởi phương thức Is…Null() hoặc gán giá trị null cho một cột bởi phương thức
Set…Null(), trong đó dấu “…” được dùng để thay thế cho tên cột muốn kiểm tra. Chẳng
hạn, IsContactNameNull() hay SetContactNameNull().

Trang 226
Giáo trình lập trình cơ sở dữ liệu

6.8. Tạo đối tượng DataAdapter bằng Visual Studio .Net


Để tạo một đối tượng DataAdapter bằng Visual Studio .Net ở chế độ thiết kết, thực
hiện theo các bước sau:
- Tạo một dự án mới. Mở Form1 bằng cách nhấp đôi vào Form1.cs trong khung
Solution Explorer.
- Mở khung Server Explorer và tạo một kết nối đến cơ sở dữ liệu Northwind của
SQL Server.
- Kéo đối tượng SqlDataAdapter từ thẻ Data của Toolbox vào Form. Một cửa sổ
mới xuất hiện, bắt đầu các bước cấu hình cho DataAdapter.

Hình 6.7. Tạo Data Adapter bằng VS.Net

- 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

Hình 6.8. Chọn kết nối 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

Hình 6.9. Chọn phương thức truy vấn cơ sở dữ liệu

Hình 6.10. Thiết lập lệnh truy vấn

Trang 229
Giáo trình lập trình cơ sở dữ liệu

Hình 6.11. Sinh ra các lệnh truy vấn khác

Hình 6.12. Kết quả tạo đối tượng Data Adapter

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

Hình 6.13. Cấu hình kết nối cho DataAdapter

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

6.9. Tạo đối tượng DataSet bằng Visual Studio .Net


Tiếp tục với dự án ở mục trước, ta tạo đối tượng DataSet bằng một trong hai cách sau:
- Kéo đối tượng DataSet từ thẻ Data của Toolbox vào Form và viết mã để hiển thị
dữ liệu lên Form bằng cách dùng phương thức Fill của đối tượng DataAdapter.
- Tự động sinh DataSet từ DataAdapter qua chức năng Generate DataSet. Để đơn
giản, ta thực hiện tạo DataSet theo cách này.

Hình 6.15. Chức năng Generate DataSet

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.

Hình 6.16. Hộp thoại Generate DataSet

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

Hình 6.17. Kết quả tạo đối tượng DataSet.

private void Form1_Load(object sender, EventArgs e)


{
sqlConnection1.Open();
sqlDataAdapter1.Fill(dataSet11, "Products");
sqlConnection1.Close();

System.Data.DataTable myDataTable =
dataSet11.Tables["Products"];

foreach (System.Data.DataRow myDataRow in myDataTable.Rows)


{
ListViewItem item =
listView1.Items.Add(myDataRow["ProductID"].ToString());

item.SubItems.Add(myDataRow["ProductName"].ToString());
item.SubItems.Add(myDataRow["UnitPrice"].ToString());
}
}
Kết quả chạy chương trình

Hình 6.18. Kết quả chạy chương trình

6.10. Kết chương

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.

Bài tập chương 6

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.

Những nội dung chính trong chương này gồm


 Các lớp DataTable, DataColumn và DataRow
 Tạo các ràng buộc cho đối tượng DataTable và DataColumn
 Tìm kiếm, trích lọc và sắp xếp các dòng trong DataTable
 Thay đổi các dòng trong DataTable và cập nhật vào cơ sở dữ liệu.
 Sử dụng Stored Procedure để thêm, xóa và cập nhật các dòng trong cơ sở dữ liệu.
 Tự động tạo lệnh truy vấn SQL dùng đối tượng CommandBuilder
 Tìm hiểu các sự kiện của DataAdapter và DataTable
 Xử lý lỗi cập nhật
 Sử dụng các giao dịch trong DataSet
 Cập nhật dữ liệu dùng DataSet có định kiểu

7.1. Lớp DataTable


Đối tượng DataTable được dùng để biểu diễn một bảng hay chứa dữ liệu trả về từ
một bảng trong cơ sở dữ liệu. Một DataSet có thể chứa nhiều DataTable. Các bảng sau
liệt kê các thuộc tính, phương thức và sự kiện của lớp DataTable.

PROPERTY TYPE DESCRIPTION


CaseSensitive bool Thuộc tính đọc và ghi. Cho biết việc so
sánh chuỗi trong các DataTable có phân
biệt chữ hoa – thường hay không.
ChildRelations DataRelationCollection Thuộc tính chỉ đọc. Lấy tập các quan hệ
DataRelationCollection) từ một bảng cha
tới một bảng con.Một đối tượng Data-
RelationCollection có thể chứa nhiều đối
tượng DataRelation.
Columns DataColumnCollection Thuộc tính chỉ đọc. Lấy danh sách các
cột (DataColumnCollection) chứa trong
một DataTable. Một DataColumnCollection
có thể chứa nhiều đối tượng DataColumn.
Constraints ConstraintCollection Thuộc tính chỉ đọc. Lấy tập các ràng

Trang 236
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


buộc (ConstraintCollection) bao gồm cả
ràng buộc khóa chính (UniqueConstraint),
khóa ngoại (ForeignKeyConstraint) trong
đối tượng DataTable.
DataSet DataSet Thuộc tính chỉ đọc. Lấy DataSet chứa
DataTable.
HasErrors bool Thuộc tính chỉ đọc. Trả về giá trị Bool
cho biết có dòng nào trong DataTable xảy
ra lỗi hay không.
PrimaryKey DataColumn[] Thuộc tính đọc và ghi. Lấy hay gán tập
các cột đóng vai trò khóa chính của
DataTable.
Rows DataRowCollection Thuộc tính chỉ đọc. Lấy danh sách các
dòng dữ liệu (DataRowCollection) được
chứa trong DataTable.
TableName string Thuộc tính đọc và ghi. Lấy hay gán tên
của đối tượng DataTable.
Bảng 7.1. Các thuộc tính của DataTable.

METHOD RETURN DESCRIPTION


TYPE
AcceptChanges() void Xác nhận tất cả các thay đổi được tạo ra trong
DataTable từ khi nó được tải hoặc từ lần gọi phương
thức AcceptChanges() sau cùng.
Clear() void Xóa tất cả các dòng có trong DataTable.
Clone() DataTable Sao chép toàn bộ cấu trúc của đối tượng DataTable
và trả về bản sao đó.
Compute() object Tính giá trị một biểu thức trên dòng hiện tại theo
điều kiện lọc.
GetChanges() DataTable Phương thức có nhiều dạng. Trả về một bản sao của
DataTable từ khi nó được tải hoặc từ lần gọi phương
thức AcceptChanges() sau cùng.
GetErrors() DataRow[] Phương thức có nhiều dạng. Trả về một bản sao của
tất cả các dòng (DataRow) xảy ra lỗi.
LoadDataRow() DataRow Tìm và cập nhật một đối tượng DataRow nào đó. Nếu
không có đối tượng nào được tìm thấy, một dòng mới
sẽ được tạo ra để chứa các giá trị muốn cập nhật.
NewRow() DataRow Tạo một dòng (DataRow) mới trong DataTable.
RejectChanges() void Hủy bỏ mọi thay đổi xảy ra trong đối tượng
DataTable từ khi nó được tạo hoặc từ lần gọi phương
thức AcceptChanges() sau cùng.
Select() DataRow[] Phương thức có nhiều dạng. Trả về mảng các đối
tượng DataRow thõa một điều kiện nào đó. Bạn có thể
gửi một chuỗi vào tham số để sắp xếp các DataRow.
Bảng 7.2. Một số phương thức của DataTable

EVENT EVENT HANDLER DESCRIPTION


ColumnChanging DataColumnChangeEventHandler Phát sinh trước khi giá trị của
một cột bị thay đổi được xác nhận.
ColumnChanged DataColumnChangeEventHandler Phát sinh sau khi giá trị của một
cột bị thay đổi được xác nhận.

Trang 237
Giáo trình lập trình cơ sở dữ liệu

EVENT EVENT HANDLER DESCRIPTION


RowChanging DataRowChangeEventHandler Phát sinh trước khi dòng bị thay
đổi được xác nhận.
RowChanged DataRowChangeEventHandler Phát sinh sau khi dòng bị thay đổi
được xác định.
RowDeleting DataRowChangeEventHandler Phát sinh trước khi một dòng bị
xóa khỏi DataTable.
RowDeleted DataRowChangeEventHandler Phát sinh sau khi một dòng bị xóa
khỏi DataTable.
Bảng 7.3. Các sự kiện trong lớp DataTable

7.2. Lớp DataRow


Đối tượng DataRow được dùng để biểu diễn một dòng trong bảng (DataTable). Một
DataTable có thể chứa nhiều đối tượng DataRow. Các bảng dưới đây liệt kê một số
thuộc tính và phương thức của lớp DataRow.

PROPERTY TYPE DESCRIPTION


HasErrors bool Returns a bool value that indicates whether any of the
DataColumn objects in the DataRow have errors.
ItemArray object[] Gets or sets all the DataColumn objects in the
DataRow.
RowState DataRowState Gets the current state of the DataRow. The state can
be Added, Deleted, Detached~FT, Modified, or
Unchanged. The state depends in the operation
performed on the DataRow and whether the
AcceptChanges() method has been called to commit the
changes.
Table DataTable Gets the DataTable object to which the DataRow
belongs.
Bảng 7.4. Các thuộc tính của DataRow

METHOD RETURN DESCRIPTION


TYPE
AcceptChanges() void Xác nhận mọi thay đổi xảy ra trên đối tượng
DataRow từ khi nó được tải hoặc từ lần gọi
phương thức AcceptChanges() sau cùng.
BeginEdit() void Bắt đầu việc cập nhật đối tượng DataRow.
CancelEdit() void Hủy bỏ việc cập nhật dữ liệu trên đối tượng
DataRow và trả về trạng thái ban đầu.
ClearErrors() void Xóa bỏ mọi lỗi xảy ra trên đối tượng DataRow
Delete() void Xóa đối tượng DataRow hiện tại.
EndEdit() void Kết thúc việc cập nhật dữ liệu cho DataRow
và xác nhận mọi thay đổi.
GetChildRows() DataRow[] Phương thức có nhiều dạng. Trả về mảng các
DataRow con của DataRow hiện tại xác định
bởi đối tượng DataRelation.
GetColumnError() string Phương thức có nhiều dạng. Trả về mô tả mỗi
xảy ra trên một đối tượng DataColumn nào đó.
GetColumnsInError() DataColumn[] Trả về mảng chứa các đối tượng DataColumn

Trang 238
Giáo trình lập trình cơ sở dữ liệu

METHOD RETURN DESCRIPTION


TYPE
xảy ra lỗi.
GetParentRow() DataRow Phương thức có nhiều dạng. Trả về một
DataRow chứa dòng cha của dòng hiện tại xác
định bởi đối tượng DataRelation.
GetParentRows() DataRow[] Phương thức có nhiều dạng. Trả về mảng các
DataRow chứa các dòng cha của dòng hiện tại
được chỉ ra bởi đối tượng DataRelation.
IsNull() bool Phương thức có nhiều dạng. Trả về một giá
trị Bool cho biết đối tượng Column nào đó có
chứa giá trị null hay không.
RejectChanges() void Hủy bỏ mọi thay đổi xảy ra trên đối tượng
DataRow từ lần gọi phương thức
AcceptChanges() sau cùng.
SetNull() void Gán giá trị null cho một DataColumn nào đó.
SetParentRow() void Phương thức có nhiều dạng. Gán một đối tượng
DataRow làm dòng cha của dòng hiện tại.
Bảng 7.5. Một số phương thức của lớp DataRow

7.3. Lớp DataColumn


Đối tượng DataColumn dùng để biểu diễn một cột của bảng (DataTable) hoặc dòng
(DataRow). Một dòng DataRow hoặc DataTable có thể chứa nhiều đối tượng
DataColumn.

PROPERTY TYPE DESCRIPTION


AllowDBNull bool Thuộc tính đọc và ghi. Cho biết cột (DataColumn)
này có thể chứa giá trị null hay không. Giá trị
mặc định là true.
AutoIncrement bool Thuộc tính đọc và ghi. Cho biết DataColumn có
thể tự động tăng giá trị cho dòng mới hay không.
Giá trị mặc định là false.
AutoIncrementSeed long Thuộc tính đọc và ghi. Lấy hay gán giá trị bắt
đầu cho đối tượng DataColumn. Chỉ áp dụng khi
thuộc tính AutoIncrement là true. Giá trị mặc
định là 0.
AutoIncrementStep long Thuộc tính đọc và ghi. Lấy hay gán bước (mức)
tăng giá trị. Chỉ áp dụng khi thuộc tính
AutoIncrement là true. Giá trị mặc định là 1.
Caption string Thuộc tính đọc và ghi. Lấy hay gán tiêu đề cột.
Tiêu đề của cột được dùng cho việc hiển thị trên
Form. Giá trị mặc định là null.
ColumnName string Thuộc tính đọc và ghi. Lấy hay gán tên của đối
tượng DataColumn.
ColumnMapping MappingType Thuộc tính đọc và ghi. Lấy hay gán đối tượng
MappingType cho DataColumn. Giá trị này xác định
cách mà DataColumn được lưu vào tài liệu XML khi
gọi phương thức WriteXml().
DataType Type Thuộc tính đọc và ghi. Lấy hay gán kiểu dữ liệu
chuẩn .Net được dùng để biểu diễn giá trị được
lưu trong DataColumn. Các kiểu này có thể là

Trang 239
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


Boolean, Byte, Char, DateTime, Decimal, Double,
Int16, Int32, Int64, SByte, Single, String,
TimeSpan, UInt16, or UInt64.
DafaultValue object Thuộc tính đọc và ghi. Lấy hay gán giá trị mặc
định cho DataColumn khi một dòng mới được tạo.
Thuộc tính này chỉ được dùng khi thuộc tính
AutoIncrement được đặt là false.
MaxLength int Thuộc tính đọc và ghi. Lấy hay gán chiều dài tối
đa của chuỗi được lưu trong DataColumn. Giá trị
mặc định là -1.
Ordinal int Thuộc tính chỉ đọc. Lấy thứ tự (vị trí) của
DataColumn. Cột đầu tiên có thứ tự là 0.
ReadOnly bool Thuộc tính đọc và ghi. Cho biết giá trị được lưu
trong DataColumn có thể thay đổi hay không. Giá
trị mặc định là false.
Table DataTable Thuộc tính chỉ đọc. Lấy DataTable chứa đối tượng
DataColumn.
Unique bool Thuộc tính đọc và ghi. Cho biết DataColumn có
thể chứa hai giá trị giống nhau trên 2 dòng của
cùng một cột hay không. Giá trị mặc định là
false.
Bảng 7.6. Một số thuộc tính của DataColumn

7.4. Tạo các ràng buộc cho DataTable và DataColumn


Trong chương trước, ta đã biết đối tượng DataSet được dùng để lưu một bản sao của
cơ sở dữ liệu. Chẳng hạn như bản sao của các dòng lấy từ các bảng của một cơ sở dữ
liệu. Mỗi bảng được biểu diễn bởi một đối tượng DataTable. Trong đó, một DataTable
lại có nhiều cột được biểu diễn bởi các DataColumn.
Mặt khác, để lưu trữ các dòng lấy từ các bảng của cơ sở dữ liệu, cần phải thêm các
ràng buộc cho các đối tượng DataTable và DataColumn. Điều này cho phép bạn mô
hình hóa ràng buộc của các cột và các bảng trong cơ sở dữ liệu (ràng buộc khóa chính,
khóa ngoại, tính duy nhất của dữ liệu) vào các đối tượng DataTable và DataColumn.
Ngoài ra, ta còn có thể thêm các ràng buộc sau cho một DataColumn:
 Một cột (DataColumn) có chấp nhận giá trị null hay không – được quy định bởi
thuộc tính AllowDBNull.
 Giá trị được lưu trong một cột có được tăng tự động hay không – được quy định
bởi các thuộc tính AutoIncrement, AutoIncrementSeed va2AutoIncrementStep.
Giá trị của các thuộc tính này được gán khi thêm một dòng vào DataTable tương
ứng với một bảng trong cơ sở dữ liệu có chứa cột định danh (Identity). Ví dụ:
Cột ProductID của bảng Products là một cột định danh.
 Chiều dài tối đa của chuỗi ký tự được gán làm giá trị của một cột – được quy
định bởi thuộc tính MaxLength của đối tượng DataColumn.
 Có thể cập nhật giá trị của một cột hay không – quy định bởi thuộc tính
ReadOnly.

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.

7.4.1. Tạo các ràng buộc bằng cách viết mã


Để tạo ràng buộc cho các đối tượng DataTable và DataColumn, ta thiết lập giá trị
cho các thuộc tính của chúng.
Ví dụ: Giả sử, bạn có một đối tượng DataSet tên là myDataSet chứa 3 đối tượng
DataTable có tên Products, Orders và Order Details lấy được sau khi thực thi đoạn mã
sau.
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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];";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

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.

7.4.2. Tạo ràng buộc cho các DataTable


Phần này trình bày cách để thêm các ràng buộc cho đối tượng DataTable. Cụ thể, ta
sẽ tìm hiểu cách tạo ràng buộc khóa chính cho các DataTable Products, Orders và Order
Details, tạo ràng buộc khóa ngoại từ Order Details DataTable tới Orders và Products
DataTable.
Trên thực tế, ràng buộc khóa chính là một dạng của (hay thực thi) ràng buộc về tính
duy nhất của dữ liệu (UNIQUE). Các ràng buôc được lưu trong một đối tượng có kiểu
ConstraintCollection. Để lấy tập ràng buộc (ConstraintCollection) từ DataTable, ta dùng
thuộc tính Constraints.
Để thêm một đối tượng Constraint vào ConstraintCollection, ta gọi phương thức
Add() từ thuộc tính Constraints của đối tượng DataTable. Phương thức Add() cho phép
bạn thêm các ràng buộc về tính duy nhất của dữ liệu, ràng buộc khóa chính và cả ràng
buộc khóa ngoại vào một DataTable.
Ngoài ra, ta có thể thêm ràng buộc khóa chính cho một DataTable bằng cách giá giá
trị cho thuộc tính PrimaryKey. Thuộc tính này chứa một mảng đối tượng DataColumn

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.

Ví dụ 2: Thêm khóa chính cho DataTable Orders


Đoạn mã sau gán DataColumn có tên OrderID cho thuộc tính PrimaryKey của
DataTable Orders.
myDataSet.Tables["Orders"].PrimaryKey =
new DataColumn[]
{
myDataSet.Tables["Orders"].Columns["OrderID"]
};

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)

// adds a primary key or unique constraint


void Add(string constraintName, DataColumn myDataColumn,
bool isPrimaryKey)

// adds a foreign key constraint


void Add(string constraintName, DataColumn parentColumn,
DataColumn childColumn)

// adds a primary key or unique constraint


void Add(string constraintName, DataColumn[] myDataColumn,
bool isPrimaryKey)

// adds a foreign key constraint


void Add(string cosntraintName, DataColumn[] parentColumns,
DataColumn[] childColumns)
Trong đó:
 constraintName: là tên của ràng buộc
 isPrimaryKey: cho biết đây là ràng buộc khóa chính hay chỉ là ràng buộc
UNIQUE thông thường.
 myDataColumn: cột (DataColumn) muốn áp dụng ràng buộc
 childColumn: cột (DataColumn) chứa khóa ngoại. Áp dụng trong trường hợp
thêm ràng buộc khóa ngoại.
 parentColumn: cột (DataColumn) chứa khóa chính. Áp dụng trong trường hợp
thêm ràng buộc khóa ngoại.
Đoạn mã sau minh họa cách sử dụng phương thức Add() để thêm một ràng buộc
khóa chính vào Products DataTable.
myDataSet.Tables["Orders"].Constraints.Add(
"Primary key constraint",
myDataSet.Tables["Orders"].Columns["OrderID"],
true );

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

Ví dụ 3: Tạo khóa chính cho DataTable Order Details


Khóa chính của bảng Order Details được tạo từ hai cột OrderID và ProductID. Đoạn
mã sau minh họa các tạo khóa chính cho DataTable Order Details bằng cách gán giá trị
cho thuộc tính PrimaryKey.

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.

Ví dụ 4: Tạo ràng buộc khóa ngoại cho DataTable Order Details.


Trong ví dụ này, ta sẽ dùng phương thức Add() trong thuộc tính Constraints của
DataTable để thêm các ràng buộc khóa ngoại cho DataTable Order Details.

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"]
);

7.4.3. Tạo ràng buộc cho DataColumn


Trong phần này, ta sẽ tìm hiểu cách để thêm các ràng buộc cho đối tượng
DataColumn được lưu trong một DataTable bằng cách thiết lập giá trị cho các thuộc
tính MaxLength, AllowDBNull, AutoIncrement, AutoIncrementSeed, AutoIncrement-
Step, ReadOnly và Unique của DataColumn.
Cột ProductID của bảng Products trong cơ sở dữ liệu là cột định danh, có giá trị
được gán và tăng tự động khi có dòng mới được thêm vào. Giá trị bắt đầu và bước tăng
đều được gán là 1. Do đó, giá trị trên cột ProductID sẽ là 1, 2, 3, …
Lưu ý:
Khi thiết lập giá trị các thuộc tính AutoIncrementSeed và AutoIncrementStep của
một DataColumn tương ứng với một cột định danh trong cơ sở dữ liệu, bạn nên đặt hai
giá trị này là -1. Theo đó, khi gọi phương thức Fill(), ADO.Net tự động tìm ra các giá trị
để gán cho AutoIncrementSeed và AutoIncrementStep dựa trên các giá trị lấy về từ cơ sở
dữ liệu.
Đoạn mã sau chỉ cách gán giá trị cho các thuộc tính của DataColumn có tên 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.

Chương trình 7.1: Tạo ràng buộc cho DataTable và DataColumn


/*
AddRestrictions.cs illustrates how to add constraints to
DataTable objects and add restrictions to DataColumn objects
*/
using System;
using System.Data;
using System.Data.SqlClient;

class AddRestrictions
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.CommandText =
"SELECT ProductID, ProductName " +
"FROM Products;" +

"SELECT OrderID " +


"FROM Orders;" +

"SELECT OrderID, ProductID, UnitPrice " +


"FROM [Order Details];";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

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

myDataSet.Tables["Table2"].TableName = "Order Details";

// set the PrimaryKey property for the Products DataTable


// to the ProductID column

DataTable productsDataTable = myDataSet.Tables["Products"];

DataColumn[] productsPrimaryKey =
new DataColumn[]
{
productsDataTable.Columns["ProductID"]
};
productsDataTable.PrimaryKey = productsPrimaryKey;

// set the PrimaryKey property for the Orders DataTable


// to the OrderID column

myDataSet.Tables["Orders"].PrimaryKey =
new DataColumn[]
{
myDataSet.Tables["Orders"].Columns["OrderID"]
};

// set the PrimaryKey property for the Order Details DataTable


// to the OrderID and ProductID columns

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
);

// add a foreign key constraint on the OrderID column


// of Order Details to the OrderID column of Orders

ForeignKeyConstraint myFKC = new ForeignKeyConstraint(


myDataSet.Tables["Orders"].Columns["OrderID"],
myDataSet.Tables["Order Details"].Columns["OrderID"]
);
myDataSet.Tables["Order Details"].Constraints.Add(myFKC);

// add a foreign key constraint on the ProductID column


// of Order Details to the ProductID column of Products

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"]
);

// set the AllowDBNull, AutoIncrement, AutoIncrementSeed,


// AutoIncrementStep, ReadOnly, and Unique properties for
// the ProductID DataColumn of the Products DataTable

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;

// set the MaxLength property for the ProductName DataColumn


// of the Products DataTable

myDataSet.Tables["Products"].
Columns["ProductName"].MaxLength = 40;

// display the details of the DataColumn objects for


// the DataTable objects

foreach (DataTable myDataTable in myDataSet.Tables)


{
Console.WriteLine("\n\nReading from the " +
myDataTable + " DataTable:\n");

// display the primary key

foreach (DataColumn myPrimaryKey in


myDataTable.PrimaryKey)
{
Console.WriteLine("myPrimaryKey = " + myPrimaryKey);
}

// display some of the details for each column

foreach (DataColumn myDataColumn in myDataTable.Columns)


{
Console.WriteLine("\nmyDataColumn.ColumnName = " +
myDataColumn.ColumnName);

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);
}
}
}
}

Kết quả chạy chương trình


Reading from the Products DataTable:

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

Reading from the Orders DataTable:

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

Reading from the Order Details DataTable:

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];";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

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

FillSchema.cs illustrates how to read schema information


using the FillSchema() method of a DataAdapter object
*/
using System;
using System.Data;
using System.Data.SqlClient;

class FillSchema
{
public static void Main()
{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.CommandText =
"SELECT ProductID, ProductName " +
"FROM Products;" +
"SELECT OrderID " +
"FROM Orders;" +
"SELECT OrderID, ProductID, UnitPrice " +
"FROM [Order Details];";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.FillSchema(myDataSet, SchemaType.Mapped);
mySqlConnection.Close();

myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Orders";
myDataSet.Tables["Table2"].TableName = "Order Details";

// display the details of the DataColumn objects for


// the DataTable objects

foreach (DataTable myDataTable in myDataSet.Tables)


{
Console.WriteLine("\n\nReading from the " +
myDataTable + " DataTable:\n");

// display the primary key


foreach (DataColumn myPrimaryKey in
myDataTable.PrimaryKey)
{
Console.WriteLine("myPrimaryKey = " + myPrimaryKey);
}

// display the constraints

foreach (Constraint myConstraint in


myDataTable.Constraints)
{
Console.WriteLine("myConstraint.IsPrimaryKey = " +

Trang 253
Giáo trình lập trình cơ sở dữ liệu

((UniqueConstraint)myConstraint).IsPrimaryKey);

foreach (DataColumn myDataColumn in


((UniqueConstraint)myConstraint).Columns)
{
Console.WriteLine("myDataColumn.ColumnName = "
+ myDataColumn.ColumnName);
}
}

// display some of the details for each column


foreach (DataColumn myDataColumn in myDataTable.Columns)
{
Console.WriteLine("\nmyDataColumn.ColumnName = " +
myDataColumn.ColumnName);

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);
}
}
}
}

Kết quả chạy chương trình


Reading from the Products DataTable:

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

Reading from the Orders DataTable:

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

Reading from the Order Details DataTable:

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.

7.5.1. Tìm kiếm một DataRow từ DataTable


a. Lấy các dòng từ cơ sở dữ liệu lưu vào một DataTable.
b. Thiết lập giá trị thuộc tính PrimaryKey của DataTable
c. Gọi phương thức Find() của DataTable và gửi giá trị trên cột khóa chính của
DataRow muốn tìm vào tham số của phương thức Find().

Đ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.

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Products");
mySqlConnection.Close();

DataTable productsDataTable = myDataSet.Tables["Products"];

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.

DataRow productDataRow = productsDataTable.Rows.Find("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

có tên orderDetailsDataTable. Để tìm DataRow có OrderID = 10248 và ProductID = 11,


ta gọi phương thức Find() như sau:
object[] orderDetails =
new object[]
{
10248,
11
};
DataRow orderDetailDataRow =
orderDetailsDataTable.Rows.Find(orderDetails);

7.5.2. Trích lọc các DataRow từ DataTable


Để lọc và sắp xếp các DataRow từ một DataTable, ta dùng phương thức Select() của
đối tượng DataTable. Phương thức này có các dạng sau:
DataRow[] Select()
DataRow[] Select(string filterExpression)
DataRow[] Select(string filterExpression, string sortExpression)
DataRow[] Select(string filterExpression, string sortExpression,
DataViewRowState myDataViewRowState)

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();

// Lấy các DataRow có giá trị trên cột ProductID <= 5

DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5");

// Lấy các DataRow có giá trị trên cột ProductID <= 5


// và sắp xếp các DataRow giảm dần theo ProductID

Trang 257
Giáo trình lập trình cơ sở dữ liệu

DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5", "ProductID DESC");

// Lấy các DataRow có giá trị trên cột ProductID <= 5


// trước khi bị xóa hoặc thay đổi
// và sắp xếp các DataRow giảm dần theo ProductID

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();

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";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();

myDataSet.Tables["Table"].TableName = "Products";
myDataSet.Tables["Table1"].TableName = "Order Details";

// set the PrimaryKey property for the Products DataTable


// to the ProductID column

DataTable productsDataTable = myDataSet.Tables["Products"];


productsDataTable.PrimaryKey =
new DataColumn[]
{
productsDataTable.Columns["ProductID"]
};

// set the PrimaryKey property for the Order Details DataTable


// to the OrderID and ProductID columns
DataTable orderDetailsDataTable =
myDataSet.Tables["Order Details"];

orderDetailsDataTable.Constraints.Add(
"Primary key constraint on the OrderID and ProductID columns",
new DataColumn[]
{
orderDetailsDataTable.Columns["OrderID"],
orderDetailsDataTable.Columns["ProductID"]
},
true
);

// find product with ProductID of 3 using the Find() method


// to locate the DataRow using its primary key value
Console.WriteLine("Using the Find() method
to locate DataRow object " +
"with a ProductID of 3");

DataRow productDataRow = productsDataTable.Rows.Find("3");

foreach (DataColumn myDataColumn in productsDataTable.Columns)


{
Console.WriteLine(myDataColumn + " = " +
productDataRow[myDataColumn]);
}

Trang 259
Giáo trình lập trình cơ sở dữ liệu

// find order with OrderID of 10248 and ProductID of 11 using


// the Find() method

Console.WriteLine("Using the Find() method


to locate DataRow object " +
"with an OrderID of 10248 and a ProductID of 11");

object[] orderDetails =
new object[]
{
10248,
11
};

DataRow orderDetailDataRow =
orderDetailsDataTable.Rows.Find(orderDetails);

foreach (DataColumn myDataColumn in


orderDetailsDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
orderDetailDataRow[myDataColumn]);
}

// filter and sort the DataRow objects in productsDataTable


// using the Select() method

Console.WriteLine("Using the Select() method


to filter and sort DataRow objects");

DataRow[] productDataRows =
productsDataTable.Select("ProductID <= 5", "ProductID DESC",
DataViewRowState.OriginalRows);

foreach (DataRow myDataRow in productDataRows)


{
foreach (DataColumn myDataColumn in
productsDataTable.Columns)
{
Console.WriteLine(myDataColumn + " = " +
myDataRow[myDataColumn]);
}
}

// filter and sort the DataRow objects in productsDataTable


// using the Select() method

Console.WriteLine("Using the Select() method


to filter and sort DataRow objects");

productDataRows =
productsDataTable.Select("ProductName LIKE 'Cha*'",
"ProductID ASC, ProductName DESC");

foreach (DataRow myDataRow in productDataRows)


{
foreach (DataColumn myDataColumn in
productsDataTable.Columns)
{

Trang 260
Giáo trình lập trình cơ sở dữ liệu

Console.WriteLine(myDataColumn + " = " +


myDataRow[myDataColumn]);
}
}
}
}

Kết quả chạy chương trình


Using the Find() method to locate DataRow object with a ProductID of 3
ProductID = 3
ProductName = Aniseed Syrup

Using the Find() method to locate DataRow object with an OrderID of


10248 and a ProductID of 11
OrderID = 10248
ProductID = 11
UnitPrice = 14
Quantity = 12

Using the Select() method to filter and sort DataRow objects


ProductID = 5
ProductName = Chef Anton's Gumbo Mix
ProductID = 4
ProductName = Chef Anton's Cajun Seasoning
ProductID = 3
ProductName = Aniseed Syrup
ProductID = 2
ProductName = Chang
ProductID = 1
ProductName = Chai

Using the Select() method to filter and sort DataRow objects


ProductID = 1
ProductName = Chai
ProductID = 2
ProductName = Chang

7.6. Cập nhật dữ liệu trên các dòng của DataTable


Trong chương trước, ta đã biết cách đọc các dòng từ cơ sở dữ liệu rồi đưa vào
DataSet bằng cách thiết lập lệnh truy vấn cho thuộc tính SelectCommand và gọi phương
thức Fill() của đối tượng DataAdapter. Chẳng hạn:
SqlCommand mySelectCommand = mySqlConnection.CreateCommand();
mySelectCommand.CommandText =
"SELECT CustomerID, CompanyName, Address " +
"FROM Customers " +
"ORDER BY CustomerID";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySelectCommand;

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;

Bốn tham số của phương thức Add() là:


 Tên của tham số
 Kiểu dữ liệu của tham số
 Chiều dài tối đa của chuỗi được gán làm giá trị của tham số
 Tên của cột tương ứng trong cơ sở dữ liệu (sẽ lưu giá trị tham số)

7.6.3. Thiết lập thuộc tính UpdateCommand


Đoạn mã sau tạo một đối tượng SqlCommand có tên myUpdateCommand chứa lệnh
truy vấn SQL UPDATE rồi gán nó cho thuộc tính UpdateCommand của DataAdapter.
SqlCommand myUpdateCommand = mySqlConnection.CreateCommand();

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;

Có hai điểm cần lưu ý trong đoạn mã này:


 Mệnh đề WHERE của lệnh UPDATE chỉ ra các tham số tương ứng với các cột
CompanyID, CompanyName và Address. Điểm này thể hiện tính đồng thời tối
ưu (Optimistic Concurrency)
 Thuộc tính SourceVersion của các tham số @OldCustomerID, @OldCompany-
Name và @OldAddress được gán giá trị DataRowVersion.Original. Việc này
làm cho giá trị của các tham số được gán cho giá trị của các cột trong DataRow
trước khi thay đổi chúng.
Hai đặc điểm này thể hiện tính đồng thời của lệnh UPDATE. Tính đồng thời xác
định cách xử lý khi có nhiều người dùng cập nhật dữ liệu trên cùng một dòng. Có hai
loại được áp dụng cho DataSet.
 Optimistic Concurrency: với tính đồng thời tối ưu, bạn chỉ có thể thay đổi một
dòng của bảng trong cơ sở dữ liệu nếu không có ai khác thay đổi nó tính từ lúc
bạn tải nó vào DataSet. Hình thức này thường được dùng và là cách tốt nhất vì
không người dùng nào muốn dữ liệu của mình bị ghi đè.

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

7.6.4. Thiết lập thuộc tính DeleteCommand


Đoạn mã sau tạo một đối tượng SqlCommand có tên myDeleteCommand chứa lệnh
DELETE rồi gán cho thuộc tính DeleteCommand của DataAdapter. Lệnh DELETE
cũng sử dụng tính đồng thời tối ưu (optimistic concurrency)

Trang 264
Giáo trình lập trình cơ sở dữ liệu

SqlCommand myDeleteCommand = mySqlConnection.CreateCommand();


myDeleteCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = @OldCustomerID " +
"AND CompanyName = @OldCompanyName " +
"AND Address = @OldAddress";

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;

7.6.5. Thêm một DataRow vào DataTable


a. Dùng phương thức NewRow() của DataTable để tạo một DataRow mới
b. Gán giá trị cho các DataColumn của DataRow mới. Bạn có thể gán giá trị null
cho DataColumn bằng phương thức SetNull(), kiểm tra giá trị trong DataColumn
nào đó có bằng null hay không bằng phương thức IsNull() của DataRow.
c. Dùng phương thức Add() từ thuộc tính Rows của DataTable để thêm DataRow
mới vào DataTable.
d. Sử dụng phương thức Update() của DataAdapter để cập nhật dòng mới vào cơ sở
dữ liệu.
Ví dụ:
Phương thức AddNewRow() sau minh họa các bước để thêm một DataRow mới vào
DataTable.
public static void AddDataRow( DataTable myDataTable,
SqlDataAdapter mySqlDataAdapter, SqlConnection mySqlConnection )
{
Console.WriteLine("\nIn AddDataRow()");

// step 1: use the NewRow() method of the DataTable to


// create a new DataRow
Console.WriteLine("Calling myDataTable.NewRow()");

DataRow myNewDataRow = myDataTable.NewRow();

Console.WriteLine("myNewDataRow.RowState = " +
myNewDataRow.RowState);

Trang 265
Giáo trình lập trình cơ sở dữ liệu

// step 2: set the values for the DataColumn objects of


// the new DataRow

myNewDataRow["CustomerID"] = "J5COM";
myNewDataRow["CompanyName"] = "J5 Company";
myNewDataRow["Address"] = "1 Main Street";

// step 3: use the Add() method through the Rows property


// to add the new DataRow to the DataTable
Console.WriteLine("Calling myDataTable.Rows.Add()");

myDataTable.Rows.Add(myNewDataRow);

Console.WriteLine("myNewDataRow.RowState = " +
myNewDataRow.RowState);

// step 4: use the Update() method to push the new


// row to the database
Console.WriteLine("Calling mySqlDataAdapter.Update()");
mySqlConnection.Open();

int numOfRows = mySqlDataAdapter.Update(myDataTable);

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

Kết quả xuất ra màn hình


In AddDataRow()
Calling myDataTable.NewRow()
myNewDataRow.RowState = Detached

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

Có thể giải thích kết quả này như sau:


- Sau khi gọi phương thức myDataTable.NewRow() tạo ra một đối tượng
myNewDataRow với thuộc tính RowState có giá trị là Detached cho biết
DataRow chưa được thêm vào DataTable.
- Sau khi gọi phương thức myDataTable.Rows.Add() để thêm myNewDataRow
vào DataTable. RowState có giá trị là Added cho biết DataRow vừa được thêm
vào DataTable.
- Cuối cùng, sau khi gọi phương thức mySqlDataAdapter.Update() để cập nhật
dòng mới vào cơ sở dữ liệu, RowState có giá trị là Unchanged. Thực tế, phương
thức Update() thực thi lệnh INSERT được gán trong thuộc tình InsertCommand
của DataAdapter.
7.6.6. Cập nhật một DataRow trong DataTable
a. Thiết lập thuộc tính PrimaryKey của DataTable.
b. Dùng phương thức Find() của DataTable để xác định DataRow muốn cập nhật
bằng cách dùng giá trị trong cột khóa chính.
c. Thay đổi giá trị trên các cột của DataRow.
d. Dùng phương thức Update() của đối tượng DataAdapter để lưu các thay đổi vào
cơ sở dữ liệu.

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.

public static void ModifyDataRow( DataTable myDataTable,


SqlDataAdapter mySqlDataAdapter, SqlConnection mySqlConnection)
{
Console.WriteLine("\nIn ModifyDataRow()");

// step 1: set the PrimaryKey property of the DataTable


myDataTable.PrimaryKey =
new DataColumn[]
{
myDataTable.Columns["CustomerID"]
};

// step 2: use the Find() method to locate the DataRow


// in the DataTable using the primary key value

DataRow myEditDataRow = myDataTable.Rows.Find("J5COM");

// step 3: change the DataColumn values of the DataRow


myEditDataRow["CompanyName"] = "Widgets Inc.";
myEditDataRow["Address"] = "1 Any Street";

Console.WriteLine("myEditDataRow.RowState = " +
myEditDataRow.RowState);

Console.WriteLine("myEditDataRow[\" CustomerID\", " +


"DataRowVersion.Original] = " +
myEditDataRow["CustomerID", DataRowVersion.Original]);

Console.WriteLine("myEditDataRow[\" CompanyName\", " +


"DataRowVersion.Original] = " +
myEditDataRow["CompanyName", DataRowVersion.Original]);

Console.WriteLine("myEditDataRow[\" Address\", " +


"DataRowVersion.Original] = " +
myEditDataRow["Address", DataRowVersion.Original]);

Console.WriteLine("myEditDataRow[\" CompanyName\", " +


"DataRowVersion.Current] = " +
myEditDataRow["CompanyName", DataRowVersion.Current]);

Console.WriteLine("myEditDataRow[\" Address\", " +


"DataRowVersion.Current] = " +
myEditDataRow["Address", DataRowVersion.Current]);

// step 4: use the Update() method to push the modified


// row to the database
Console.WriteLine("Calling mySqlDataAdapter.Update()");
mySqlConnection.Open();

int numOfRows = mySqlDataAdapter.Update(myDataTable);

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);
}

Kết quả xuất ra màn hình


In ModifyDataRow()
myEditDataRow.RowState = Modified
myEditDataRow["CustomerID", DataRowVersion.Original] = J5COM
myEditDataRow["CompanyName", DataRowVersion.Original] = J5 Company
myEditDataRow["Address", DataRowVersion.Original] = 1 Main Street
myEditDataRow["CompanyName", DataRowVersion.Current] = Widgets Inc.
myEditDataRow["Address", DataRowVersion.Current] = 1 Any Street

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["CompanyName"] = "Widgets Inc.";


myEditDataRow["Address"] = "1 Any Street";

myEditDataRow.EndEdit();

7.6.7. Xóa một DataRow ra khỏi DataTable


a. Thiết lập thuộc tính PrimaryKey cho đối tượng DataTable
b. Sử dụng phương thức Find() để xác định DataRow cần xóa
c. Gọi phương thức Delete() để xóa DataRow
d. Dùng phương thức Update() để cập nhật các thay đổi vào cơ sở dữ liệu.
Ví dụ:
Phương thức RemoveDataRow() sau đây minh họa các bước để tìm và xóa DataRow đã
được cập nhật trong ví dụ trước.

public static void RemoveDataRow(DataTable myDataTable,


SqlDataAdapter mySqlDataAdapter, SqlConnection mySqlConnection)
{
Console.WriteLine("\nIn RemoveDataRow()");

// step 1: set the PrimaryKey property of the DataTable


myDataTable.PrimaryKey =

Trang 269
Giáo trình lập trình cơ sở dữ liệu

new DataColumn[]
{
myDataTable.Columns["CustomerID"]
};

// step 2: use the Find() method to locate the DataRow

DataRow myRemoveDataRow = myDataTable.Rows.Find("J5COM");

// step 3: use the Delete() method to remove the DataRow


Console.WriteLine("Calling myRemoveDataRow.Delete()");

myRemoveDataRow.Delete();

Console.WriteLine("myRemoveDataRow.RowState = " +
myRemoveDataRow.RowState);

// step 4: use the Update() method to remove the deleted


// row from the database
Console.WriteLine("Calling mySqlDataAdapter.Update()");
mySqlConnection.Open();

int numOfRows = mySqlDataAdapter.Update(myDataTable);

mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
Console.WriteLine("myRemoveDataRow.RowState = " +
myRemoveDataRow.RowState);
}

Kết quả xuất ra màn hình


In RemoveDataRow()
Calling myRemoveDataRow.Delete()
myRemoveDataRow.RowState = Deleted

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ề.

7.8. Cập nhật dữ liệu dùng Stored Procedure


Ngoài cách dùng các đối tượng Command chứa trực tiếp các lệnh INSERT,
UPDATE và DELETE, DataAdapter còn cho phép gọi các thủ tục (Stored Procedure)
để thêm, cập nhật và xóa các dòng khỏi một cơ sở dữ liệu. Khả năng gọi các thủ tục của
DataAdapter là một trong những tính năng rất mạnh của ADO.Net.
Chẳng hạn, bạn có thể sử dụng thủ tục để thêm một dòng vào bảng có chứa cột định
danh. Sau đó, lấy về giá trị mới được cơ sở dữ liệu sinh ra trên cột này. Bạn cũng có thể
cập nhật hay xóa một dòng bởi Stored Procedure.
Việc sử dụng Stored Procedure thay cho các lệnh INSERT, UPDATE và DELETE
cũng làm cải thiện tốc độ xử lý của chương trình. Nếu cơ sở dữ liệu có hỗ trợ Stored
Procedure, bạn nên tận dụng sức mạnh của nó. Với Oracle, Stored Procedure được viết
dưới dạng PL/SQL.
Ví dụ: Trong ví dụ này, ta cần thực hiện những việc sau:
 Tạo các Stored Procedure cần thiết trong cơ sở dữ liệu Northwind.
 Tạo một đối tượng DataAdapter để gọi các Stored Procedure.
 Thêm, cập nhật và xóa một DataRow từ DataTable.

Đầu tiên, ta tạo ra 3 Stored Procedure trong cơ sở dữ liệu Northwind:


 AddProduct4() dùng để thêm một dòng mới vào bảng Products
 UpdateProduct() dùng để cập nhật một dòng trong bảng Products
 DeleteProduct() dùng để xóa một dòng khỏi bảng Products.

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
*/

CREATE PROCEDURE AddProduct4

Trang 272
Giáo trình lập trình cơ sở dữ liệu

@MyProductName nvarchar(40),
@MyUnitPrice money
AS

-- declare the @MyProductID variable


DECLARE @MyProductID int

-- insert a row into the Products table

INSERT INTO Products ( ProductName, UnitPrice )


VALUES ( @MyProductName, @MyUnitPrice )

-- use the SCOPE_IDENTITY() function to get the last


-- identity value inserted into a table performed within
-- the current database session and stored procedure,
-- so SCOPE_IDENTITY returns the ProductID for the new row
-- in the Products table in this case

SET @MyProductID = SCOPE_IDENTITY()

RETURN @MyProductID

/*
UpdateProduct.sql creates a procedure that modifies a row
in the Products table using values passed as parameters
to the procedure
*/

CREATE PROCEDURE UpdateProduct


@OldProductID int,
@NewProductName nvarchar(40),
@NewUnitPrice money,
@OldProductName nvarchar(40),
@OldUnitPrice money
AS

-- update the row in the Products table


UPDATE Products
SET
ProductName = @NewProductName,
UnitPrice = @NewUnitPrice

WHERE ProductID = @OldProductID


AND ProductName = @OldProductName
AND UnitPrice = @OldUnitPrice

/*
DeleteProduct.sql creates a procedure that removes a row
from the Products table
*/

CREATE PROCEDURE DeleteProduct


@OldProductID int,
@OldProductName nvarchar(40),
@OldUnitPrice money

Trang 273
Giáo trình lập trình cơ sở dữ liệu

AS

-- delete the row from the Products table


DELETE FROM Products
WHERE ProductID = @OldProductID
AND ProductName = @OldProductName
AND UnitPrice = @OldUnitPrice

/*
DeleteProduct2.sql creates a procedure that removes a row
from the Products table
*/
CREATE PROCEDURE DeleteProduct2
@OldProductID int,
@OldProductName nvarchar(40),
@OldUnitPrice money
AS

-- delete the row from the Products table


DELETE FROM Products
WHERE ProductID = @OldProductID
AND ProductName = @OldProductName
AND UnitPrice = @OldUnitPrice

-- use SET NOCOUNT ON to suppress the return of the


-- number of rows affected by the INSERT statement
SET NOCOUNT ON

-- add a row to the Audit table


IF @@ROWCOUNT = 1
INSERT INTO ProductAudit ( Action )
VALUES (
'Product deleted with ProductID of ' +
CONVERT(nvarchar, @OldProductID)
)
ELSE
INSERT INTO ProductAudit ( Action )
VALUES (
'Product with ProductID of ' +
CONVERT(nvarchar, @OldProductID) + ' was not deleted'
)

7.8.1. Sử dụng SET NOCOUNT ON trong Stored Procedure


Lệnh SET NOCOUNT ON được dùng để ngăn T-SQL trả về số dòng bị ảnh hưởng
bởi một truy vấn. Thông thường, DataAdapter sử dụng số dòng bị ảnh hưởng để cho
biết lệnh thực thi thành công hay không. Vì thế, nên tránh việc sử dụng SET
NOCOUNT ON trong Stored Procedure.
Tuy nhiên, có một trường hợp mà bạn phải sử dụng lệnh SET NOCOUNT ON. Đó
là khi Stored Procedure thực hiện các lệnh INSERT, UPDATE hay DELETE có ảnh
hưởng đến các bảng khác.

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.

7.8.2. Gọi Stored Procedure từ đối tượng DataAdapter


Để gọi các Stored Procedure, ta cũng phải tạo một đối tượng DataAdapter và gán các
Command thích hợp cho các thuộc tính SelectCommand, InsertCommand,
UpdateCommand và DeleteCommand. Tuy nhiên, thay vì thực thi các lệnh SQL trực
tiếp, những Command này sẽ gọi các thủ tục được tạo từ SQL Server.
Đoạn mã sau tạo một đối tượng SqlCommand chứa lệnh SELECT và gán cho thuộc
tính SelectCommand của SqlDataAdapter.
SqlCommand mySelectCommand = mySqlConnection.CreateCommand();
mySelectCommand.CommandText =
"SELECT " +
" ProductID, ProductName, UnitPrice " +
"FROM Products " +
"ORDER BY ProductID";
SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();
mySqlDataAdapter.SelectCommand = mySelectCommand;

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

SqlCommand myUpdateCommand = mySqlConnection.CreateCommand();


myUpdateCommand.CommandText =
"EXECUTE UpdateProduct @OldProductID, @NewProductName, " +
"@NewUnitPrice, @OldProductName, @OldUnitPrice";

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

SqlCommand myDeleteCommand = mySqlConnection.CreateCommand();


myDeleteCommand.CommandText =
"EXECUTE DeleteProduct @OldProductID, @OldProductName,
@OldUnitPrice";

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

7.8.3. Thêm một DataRow vào DataTable


Trước hết, tạo một đối tượng DataSet tên myDataSet và gọi phương thức Fill() của
DataAdapter để đổ dữ liệu lấy từ bảng Products vào.
DataSet myDataSet = new DataSet();
mySqlConnection.Open();
int numOfRows = mySqlDataAdapter.Fill(myDataSet, "Products");
mySqlConnection.Close();

Để 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

DataRow myNewDataRow = myDataTable.NewRow();

// step 2: set the values for the DataColumn objects of


// the new DataRow

myNewDataRow["ProductName"] = "Widget";
myNewDataRow["UnitPrice"] = 10.99;

// step 3: use the Add() method through the Rows property


// to add the new DataRow to the DataTable

myDataTable.Rows.Add(myNewDataRow);

// step 4: use the Update() method to push the new


// row to the database
mySqlConnection.Open();

int numOfRows = mySqlDataAdapter.Update(myDataTable);


mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);

// return the ProductID of the new DataRow


return (int)myNewDataRow["ProductID"];
}

7.8.4. Cập nhật dữ liệu trên một DataRow của DataTable


Phương thức ModifyDataRow() minh họa 4 bước để thay đổi dữ liệu trên một
DataRow của DataTable. Phương thức này sẽ cập nhật giá trị trên hai cột ProductName
và UnitPrice của một sản phẩm có mã được truyền qua tham số productID.
public static void ModifyDataRow( DataTable myDataTable,
int productID, SqlDataAdapter mySqlDataAdapter,
SqlConnection mySqlConnection )
{
// step 1: set the PrimaryKey property of the DataTable

Trang 277
Giáo trình lập trình cơ sở dữ liệu

myDataTable.PrimaryKey =
new DataColumn[]
{
myDataTable.Columns["ProductID"]
};

// step 2: use the Find() method to locate the DataRow


// in the DataTable using the primary key value

DataRow myEditDataRow = myDataTable.Rows.Find(productID);

// step 3: change the DataColumn values of the DataRow

myEditDataRow["ProductName"] = "Advanced Widget";


myEditDataRow["UnitPrice"] = 24.99;

// step 4: use the Update() method to push the update


// to the database
mySqlConnection.Open();

int numOfRows = mySqlDataAdapter.Update(myDataTable);

mySqlConnection.Close();
Console.WriteLine("numOfRows = " + numOfRows);
}

7.8.5. Xóa một DataRow khỏi DataTable


Phương thức RemoveDataRow() minh họa 4 bước để xóa một dòng khỏi DataTable.
Phương thức này chứa một tham số productID cho biết mã sản phẩm muốn xóa.
public static void RemoveDataRow( DataTable myDataTable,
int productID, SqlDataAdapter mySqlDataAdapter,
SqlConnection mySqlConnection )
{
// step 1: set the PrimaryKey property of the DataTable
myDataTable.PrimaryKey =
new DataColumn[]
{
myDataTable.Columns["ProductID"]
};

// step 2: use the Find() method to locate the DataRow

DataRow myRemoveDataRow = myDataTable.Rows.Find(productID);

// step 3: use the Delete() method to remove the DataRow

myRemoveDataRow.Delete();

// step 4: use the Update() method to push the delete


// to the database
mySqlConnection.Open();

int numOfRows = mySqlDataAdapter.Update(myDataTable);

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.

7.9. Tự động phát sinh truy vấn SQL


Trong những phần trước, việc cung cấp chi tiết các lệnh truy vấn INSERT,
UPDATE và DELETE hoặc các Stored Procedure tương ứng để cập nhật các thay đổi từ
một DataSet vào cơ sở dữ liệu buộc chúng ta phải viết khá nhiều mã lệnh.
Đối tượng CommandBuilder có thể giải quyết vấn đề này bằng cách tự động sinh ra
các lệnh INSERT, UPDATE và DELETE để thực hiện cùng một nhiệm vụ trên. Sau đó,
các lệnh này được gán cho thuộc tính InsertCommand, UpdateCommand và
DeleteCommand của đối tượng DataAdapter. Khi có một thay đổi được tạo ra trong
DataSet và phương thức Update() của DataAdapter được gọi, các lệnh này sẽ được thực
thi để lưu các thay đổi đó vào cơ sở dữ liệu.
Tuy nhiên, CommandBuilder cũng có một vài giới hạn nhất định.
 Thuộc tính SelectCommand của DataAdapter chỉ có thể nhận dữ liệu từ 1 bảng.
 Bảng trong cơ sở dữ liệu được dùng trong SelectCommand phải chứa một khóa
chính.
 Khóa chính của bảng phải nằm trong lệnh SELECT của SelectCommand.
 CommandBuilder tốn một khoảng thời gian nhất định để sinh ra các lệnh vì nó
phải khảo sát cấu trúc của cơ sở dữ liệu. Đây là yếu tố làm giảm hiệu suất của
chương trình. Vì thế, nên tránh dùng đối tượng CommandBuilder.

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 );

7.10. Các sự kiện của DataAdapter

EVENT EVENT HANDLER DESCRIPTION


FillError FillErrorEventHandler Phát sinh khi có lỗi xảy ra trong quá
trình gọi phương thức Fill().
RowUpdating RowUpdatingEventHandler Phát sinh trước khi thêm, cập nhật hay xóa
một hàng khỏi cơ sở dữ liệu bằng cách gọi
phương thức Update().
RowUpdated RowUpdatedEventHandler Phát sinh sau khi thêm, cập nhật hay xóa
một hàng khỏi cơ sở dữ liệu bằng cách gọi
phương thức Update().
Bảng 7.11. Các sự kiện của DataAdapter

7.10.1. Sự kiện FillError


Sự kiện này phát sinh khi có lỗi xảy ra trong lúc gọi phương thức Fill() của
DataAdapter. Có hai tình huống có thể gây ra lỗi:
 Cố gắng thêm một số lấy được từ cơ sở dữ liệu vào DataColumn nhưng không
thể chuyển được sang kiểu dữ liệu .Net của DataColumn mà không làm mất mất
dữ liệu.
 Cố gắng thêm một dòng lấy được từ cơ sở dữ liệu vào DataTable nhưng vi phạm
các ràng buộc được định nghĩa trong DataTable.

Phương thức sau minh họa các xử lý sự kiện FillError


// Đăng ký trình xử lý sự kiện
mySqlDataAdapter.FillError +=
new FillErrorEventHandler(FillErrorEventHandler);

// 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;
}
}

Đối số thứ hai của phương thức FillErrorEventHandler có kiểu FillErrorEventArgs


kế thừa từ lớp EventArgs.

PROPERTY TYPE DESCRIPTION


Continue bool Thuộc tính đọc và ghi. Cho biết có tiếp tục đưa dữ liệu
vào DataSet ngay cả khi có lỗi xảy ra hay không. Giá
trị mặc định là false.
DataTable DataTable Thuộc tính chỉ đọc. Lấy DataTable đang được gán dữ liệu
khi có lỗi xảy ra.
Errors Exception Thuộc tính chỉ đọc. Lấy đối tượng Exception chứa thông
tin lỗi đã xảy ra.
Values object[] Thuộc tính chỉ đọc. Lấy các giá trị trên một DataColumn
của DataRow xảy ra lỗi. Thuộc tính này trả về một mảng
các object.
Bảng 7.12. Một số thuộc tính của lớp FillErrorEventArgs

7.10.2. Sự kiện RowUpdating


Sự kiện RowUpdating phát sinh trước khi một dòng được cập nhật vào cơ sở dữ liệu
bởi việc gọi phương thức Update() của DataAdapter. Sự kiện này phát sinh trên mỗi
DataRow đã được thêm vào, sửa đổi hay bị xóa khỏi DataTable.
Những thao tác sau sẽ được thực hiện trên mỗi DataRow khi gọi phương thức
Update() của đối tượng DataAdapter.
a. Các giá trị trong DataRow được sao chép vào giá trị của các tham số của
Command thích hợp trong các thuộc tính InsertCommand, UpdateCommand
hoặc DeleteCommand của DataAdapter.
b. Sự kiện RowUpdating của DataAdapter được phát sinh
c. Đối tượng Command thực thi lệnh để cập nhật thay đổi vào cơ sở dữ liệu.
d. Các tham số OUTPUT của Command được trả về.
e. Sự kiện RowUpdated của DataAdapter được phát sinh
f. Phương thức AcceptChanges() của DataRow được gọi.

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

PROPERTY TYPE DESCRIPTION


Errors Exception Thuộc tính chỉ đọc. Lấy đối tượng Exception
chứa thông tin về lỗi đã xảy ra.
Row DataRow Thuộc tính chỉ đọc. Lấy các DataRow được gửi
tới cơ sở dữ liệu bởi phương thức Update().
StatementType StatementType Thuộc tính chỉ đọc. Lấy dạng của lệnh truy vấn
SQL được thực thi. StatementType là một kiểu
enum nằm trong namespace System.Data và nhận
một trong các giá trị sau:

 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:

 Continue: chỉ cho DataAdapter tiếp tục xử


lý các dòng còn lại.
 ErrorsOccurred: có nghĩa là việc cập nhật
được xem như một lỗi.
 SkipAllRemainingRows: chỉ ra rằng, dòng
hiện tại và các dòng còn lại sẽ bị bỏ
qua, không cập nhật.
 SkipCurrentRow: bỏ qua, không cập nhật
dòng hiện tại.

Giá trị mặc định là Continue.


TableMapping DataTableMapping Thuộc tính chỉ đọc. Lấy đối tượng
DataTableMapping được gửi tới phương phức
Update(). Đối tượng DataTableMapping chứa một
mô tả về các quan hệ ánh xạ giữa một bảng trong
cơ sở dữ liệu với một DataTable.
Bảng 7.13. Các thuộc tính của lớp SqlRowUpdatingEventArgs

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.

// Đăng ký trình xử lý sự kiện


mySqlDataAdapter.RowUpdating +=
new SqlRowUpdatingEventHandler(RowUpdatingEventHandler);

public static void RowUpdatingEventHandler(


object sender, SqlRowUpdatingEventArgs mySRUEA )
{
Console.WriteLine("\nIn RowUpdatingEventHandler()");

if ((mySRUEA.StatementType == StatementType.Insert) &&


(mySRUEA.Row["CustomerID"] == "J5COM"))
{
Console.WriteLine("Skipping current row");

Trang 282
Giáo trình lập trình cơ sở dữ liệu

mySRUEA.Status = UpdateStatus.SkipCurrentRow;
}
}

7.10.3. Sự kiện RowUpdated


Sự kiện RowUpdated phát sinh sau khi một dòng được cập nhật vào cơ sở dữ liệu
bởi việc gọi phương thức Update() của DataAdapter. Sự kiện này được phát sinh trên
mỗi DataRow sau khi được thêm vào, cập nhật hay bị xóa khỏi DataTable.
Tham số thứ hai của trình xử lý sự kiện RowUpdate có kiểu
SqlRowUpdatedEventArgs. Các thuộc tính của lớp SqlRowUpdatedEventArgs được liệt
kê trong bảng sau.
PROPERTY TYPE DESCRIPTION
RecordsAffected int Thuộc tính chỉ đọc. Trả về số nguyên cho biết số dòng
đã được thêm vào, cập nhập hay bị xóa khi các Command
thích hợp được thực thi bởi phương thức Update().
Bảng 7.14. Các thuộc tính của lớp SqlRowUpdatedEventArgs
Ví dụ sau minh họa một trình xử lý cho sự kiện RowUpdated và hiển thị số dòng bị
ảnh hưởng bởi việc thực thi đối tượng Command.

// Đăng ký trình xử lý sự kiện


mySqlDataAdapter.RowUpdated +=
new SqlRowUpdatedEventHandler(RowUpdatedEventHandler);

public static void RowUpdatedEventHandler(


object sender, SqlRowUpdatedEventArgs mySRUEA)
{
Console.WriteLine("\nIn RowUpdatedEventHandler()");
Console.WriteLine("mySRUEA.RecordsAffected = " +
mySRUEA.RecordsAffected);
}

7.11. Các sự kiện của DataTable

EVENT EVENT HANDLER DESCRIPTION


ColumnChanging DataColumnChangeEventHandler Phát sinh trước khi giá trị bị
thay đổi trong Datacolumn trên một
DataRow được xác nhận.
ColumnChanged DataColumnChangeEventHandler Phát sinh sau khi giá trị bị thay
đổi trong Datacolumn trên một
DataRow được xác nhận.
RowChanging DataRowChangeEventHandler Phát sinh trước khi DataRow bị
thay đổi trong một DataTable được
xác nhận.
RowChanged DataRowChangeEventHandler Phát sinh sau khi DataRow bị thay
đổi trong một DataTable được xác
nhận.
RowDeleting DataRowChangeEventHandler Phát sinh trước khi một DataRow bị
xóa khỏi DataTable.

Trang 283
Giáo trình lập trình cơ sở dữ liệu

EVENT EVENT HANDLER DESCRIPTION


RowDeleted DataRowChangeEventHandler Phát sinh sau khi một DataRow bị
xóa khỏi DataTable.
Bảng 7.15. Các sự kiện của lớp DataTable

7.11.1. Sự kiện ColumnChanging và ColumnChanged


Sự kiện ColumnChanging phát sinh trước khi thay đổi giá trị trên một DataColumn
của một DataRow nào được xác nhận (commit). Tương tự, sự kiện ColumnChanged
phát sinh sau khi thay đổi giá trị trên một DataColumn của một DataRow nào đó được
xác nhận. Hai sự kiện này luôn phát sinh trước sự kiện RowChanging và RowChanged.
Ý nghĩa của từ “xác nhận” (commit) trong trường hợp này được hiểu như sau: Nếu
trực tiếp gán giá trị mới cho một DataColumn, thay đổi này được DataRow tự động xác
nhận. Tuy nhiên, nếu bắt đầu thay đổi giá trị trên một DataRow bằng cách dùng phương
thức BeginEdit() thì các thay đổi chỉ được xác nhận khi phương thức EndEdit() được
gọi. Ta cũng có thể từ chối các thay đổi trên DataRow bằng cách gọi hàm CancelEdit().
Tham số thứ hai trong hàm xử lý sự kiện ColumnChanging và ColumnChanged của
DataTable đều có kiểu DataColumnChangeEventArgs. Các thuộc tính của lớp này được
liệt kê trong bảng 7.16.

PROPERTY TYPE DESCRIPTION


Column DataColumn Lấy DataColumn có giá trị bị thay đổi.
ProposedValue object Lấy hay gán giá trị mới cho DataColumn.
Row DataRow Lấy hay gán DataRow chứa DataColumn có giá trị bị
thay đổi.
Bảng 7.16. Các thuộc tính của lớp DataColumnChangeEventArgs
Ví dụ:
Đoạn mã sau minh họa hai phương thức xử lý các sự kiện ColumnChanging và
ColumnChanged. Trong mỗi phương thức, ta hiển thị giá trị của các thuộc tính Column
và ProposedValue.
DataTable customersDataTable = myDataSet.Tables["Customers"];

// Đăng ký trình xử lý sự kiện


customersDataTable.ColumnChanging +=
new DataColumnChangeEventHandler(ColumnChangingEventHandler);

customersDataTable.ColumnChanged +=
new DataColumnChangeEventHandler(ColumnChangedEventHandler);

public static void ColumnChangingEventHandler(


object sender, DataColumnChangeEventArgs myDCCEA )
{
Console.WriteLine("myDCCEA.Column = " + myDCCEA.Column);
Console.WriteLine("myDCCEA.ProposedValue = " +
myDCCEA.ProposedValue);
}

public static void ColumnChangedEventHandler(


object sender, DataColumnChangeEventArgs myDCCEA )

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);
}

7.11.2. Sự kiện RowChanging và RowChanged


Sự kiện RowChanging phát sinh trước khi một thay đổi trên DataRow được xác
nhận trong DataTable. Tương tự, sự kiện RowChanged phát sinh sau khi một thay đổi
trên DataRow được xác nhận trong DataTable.
Tham số thứ hai trong phương thức xử lý sự kiện RowChanging và RowChanged
của DataTable là một đối tượng thuộc lớp DataRowChangeEventArgs. Các thuộc tính
của lớp này được liệt kê trong bảng 7.17.
PROPERTY TYPE DESCRIPTION
Action DataRowAction Thuộc tính chỉ đọc. Lấy đối tượng DataRowAction cho
biết thao tác xử lý đã thực hiện trên DataRow. Thuộc
tính này có kiệu enum DataRowAction và được định
nghĩa trong namespace System.Data. Enum này chứa các
giá trị sau:

 Add: DataRow mới được thêm vào DataTable.


 Change: Giá trị trên DataRow đã bị thay đổi.
 Commit: Các thay đổi trên DataRow đã được xác
nhận vào DataTable.
 Delete: DataRow đã bị xóa khỏi DataTable.
 Nothing: DataRow chưa bị thay đổi.
 Rollback: Các thay đổi trên DataRow đã bị hủy.

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.

// Đăng ký trình xử lý sự kiện


customersDataTable.RowChanging +=
new DataRowChangeEventHandler(RowChangingEventHandler);

customersDataTable.RowChanged +=
new DataRowChangeEventHandler(RowChangedEventHandler);

public static void RowChangingEventHandler(


object sender, DataRowChangeEventArgs myDRCEA )
{
Console.WriteLine("myDRCEA.Action = " + myDRCEA.Action);
}

public static void RowChangedEventHandler(


object sender, DataRowChangeEventArgs myDRCEA )

Trang 285
Giáo trình lập trình cơ sở dữ liệu

{
Console.WriteLine("myDRCEA.Action = " + myDRCEA.Action);
}

7.11.3. Sự kiện RowDeleting và RowDeleted


Sự kiện RowDeleting phát sinh trước khi một DataRow bị xóa khỏi DataTable.
Tương tự, sự kiện RowDeleted phát sinh sau khi một DataRow bị xóa khỏi DataTable.
Tham số thứ hai trong phương thức xử lý sự kiện RowDeleting hay RowDeleted của
DataTable là một đối tượng có kiểu DataRowChangeEventArgs.
Ví dụ:
Đoạn mã sau cho thấy hai phương thức xử lý sự kiện RowDeleting và RowDeleted.
Các phương thức này lấy giá trị của thuộc tính Action và xuất ra màn hình.

// Đăng ký trình xử lý sự kiện


customersDataTable.RowDeleting +=
new DataRowChangeEventHandler(RowDeletingEventHandler);

customersDataTable.RowDeleted +=
new DataRowChangeEventHandler(RowDeletedEventHandler);

public static void RowDeletingEventHandler(


object sender, DataRowChangeEventArgs myDRCEA )
{
Console.WriteLine("myDRCEA.Action = " + myDRCEA.Action);
}

public static void RowDeletedEventHandler(


object sender, DataRowChangeEventArgs myDRCEA )
{
Console.WriteLine("myDRCEA.Action = " + myDRCEA.Action);
}

7.12. Xử lý lỗi cập nhật


Những ví dụ đã trình bày trên đây đều được giả định việc cập nhật các thay đổi vào
cơ sở dữ liệu bằng phương thức Update() luôn thành công. Tuy nhiên, điều này không
phải lúc nào cũng đúng.
Ví dụ:
Giả sử thuộc tính CommandText trong UpdateCommand của SqlDataAdapter được
gán như sau:

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.

Xét một tình huống xảy ra lỗi cập nhật


a. User 1 lấy dữ liệu từ bảng Customers đổ vào DataTable tên customersDataTable
b. User 2 cũng lấy các dòng đó từ bảng Customers.
c. User 1 cập nhật các cột CustomerName và CustomerID và lưu các thay đổi vào
cơ sở dữ liệu. (J5 Company  Updated Company, CustomerID = J5COM)
d. User 2 cũng cập nhật trên cùng DataRow và thay đổi giá trị cột CompanyName
từ J5 Company sang Widgets Inc. và cố gắng lưu các thay đổi vào cơ sở dữ liệu.
Khi đó, User 2 gây ra một lỗi DBConcurrencyException để thông báo lỗi cập
nhật. Lỗi này này cũng xảy ra khi User 2 cố tình cập nhật hay xóa một xong đã bị
xóa bởi User 1.

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.

7.12.1. Kiểm tra lỗi


Khi thuộc tính ContinueUpdateOnError của DataAdapter được gán là truy, không có
thông báo lỗi nào được phát đi khi có lỗi xảy ra. Thay vào đó, bạn phải kiểm tra có lỗi
xảy ra trong DataSet, DataTable hoặc DataRow hay không bằng cách dùng thuộc tính
HasError. Sau đó, hiển thị cho người dùng biết chi tiết các lỗi bằng cách dùng thuộc tính
RowError của DataRow kèm theo giá trị ban đầu và giá trị hiện tại của các DataColumn
trong DataRow. Chẳng hạn:
if (myDataSet.HasErrors)
{
Console.WriteLine("\nDataSet has errors!");

foreach (DataTable myDataTable in myDataSet.Tables)


{

Trang 287
Giáo trình lập trình cơ sở dữ liệu

// check the HasErrors property of myDataTable


if (myDataTable.HasErrors)
{
foreach (DataRow myDataRow in myDataTable.Rows)
{
// check the HasErrors property of myDataRow
if (myDataRow.HasErrors)
{

Console.WriteLine("Here is the row error:");


Console.WriteLine(myDataRow.RowError);
Console.WriteLine("Here are the column details
in the DataSet:");

foreach (DataColumn myDataColumn in myDataTable.Columns)


{
Console.WriteLine(myDataColumn + "original value = "
+ myDataRow[myDataColumn, DataRowVersion.Original]);

Console.WriteLine(myDataColumn + "current value = "


+ myDataRow[myDataColumn, DataRowVersion.Current]);
}

} // End If DataRow
} // End For Each DataRow
} // End If DataTable
} // End For Each DataTable
}

7.12.2. Sửa lỗi


Việc hiển thị lỗi cho người dùng biết chưa giải quyết hết được vấn đề. Vậy cần phải
làm gì để sửa lỗi? Giải pháp là gọi lại phương thức Fill() để đồng bộ dữ liệu từ DataSet
với cơ sở dữ liệu. Theo đó, các dòng đã được cập nhật sẽ được bởi người dùng khác sẽ
được lấy về và thay thế cho dòng tương ứng đang tồn tại trong DataSet. Chẳng hạn:
mySqlConnection.Open();
numOfRows =
mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();

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 đó.

7.13. Sử dụng các giao dịch với DataSet


Trong chương 1, ta đã biết các giao dịch là một tập hợp các lệnh truy vấn SQL. Các
giao dịch phải được xác nhận hoặc hủy bỏ để quay lại thời điểm trước khi xảy ra giao

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.

7.13.1. Sử dụng thuộc tính Transaction của các DataAdapter Command


Một DataAdapter lưu trữ 4 đối tượng Command trong 4 thuộc tính:
SelectCommand, InsertCommand, UpdateCommand và DeleteCommand. Khi phương
thức Update() của DataAdapter được gọi, các Command thích hợp như InsertCommand,
UpdateCommand hoặc DeleteCommand.
Bạn có thể tạo một đối tượng Transaction và gán nó cho thuộc tính Transaction của
các Command trong DataAdapter. Khi bạn tạo ra các thay đổi trên DataSet và gọi
phương thức Update() của DataAdapter để lưu các thay đổi, giao dịch trong các
Command sẽ được dùng.
Đoạn mã sau minh họa cách tạo một đối tượng SqlTransaction và gán cho thuộc tính
Transaction của các Command trong SqlDataAdapter. Sau đó, gọi phương thức Update
để cập nhật các dòng mới, dòng bị thay đổi hay đã bị xóa vào cơ sở dữ liệu.

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.

7.13.2. Cập nhật dữ liệu trong DataSet có định kiểu


Trong chương trước, ta đã tìm hiểu cách tạo và sử dụng một DataSet có định kiểu
trong VS.Net. Một trong những tính năng của đối tượng DataSet có định kiểu là cho
phép đọc dữ liệu từ các các cột thông qua các thuộc tính có cùng tên cột.
Trang 289
Giáo trình lập trình cơ sở dữ liệu

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");

// get the Customers DataTable

MyDataSet.CustomersDataTable myDataTable =
myDataSet1.Customers;

// create a new DataRow in myDataTable using the


// NewCustomersRow() method of myDataTable

MyDataSet.CustomersRow myDataRow =
myDataTable.NewCustomersRow();

// set the CustomerID, CompanyName, and Address of myDataRow


myDataRow.CustomerID = "J5COM";
myDataRow.CompanyName = "J5 Company";
myDataRow.Address = "1 Main Street";

// add the new row to myDataTable using the


// AddCustomersRow() method

myDataTable.AddCustomersRow(myDataRow);

// push the new row to the database using


// the Update() method of sqlDataAdapter1

sqlDataAdapter1.Update(myDataTable);

// find the row using the FindByCustomerID()


// method of myDataTable

myDataRow = myDataTable.FindByCustomerID("J5COM");

Trang 290
Giáo trình lập trình cơ sở dữ liệu

// modify the CompanyName and Address of myDataRow


myDataRow.CompanyName = "Widgets Inc.";
myDataRow.Address = "1 Any Street";

// push the modification to the database


sqlDataAdapter1.Update(myDataTable);

// display the DataRow objects in myDataTable


// in the listView1 object

foreach (MyDataSet.CustomersRow myDataRow2 in myDataTable.Rows)


{
listView1.Items.Add(myDataRow2.CustomerID);
listView1.Items.Add(myDataRow2.CompanyName);

// if the Address is null, set Address to "Unknown"


if (myDataRow2.IsAddressNull() == true)
{
myDataRow2.Address = "Unknown";
}
listView1.Items.Add(myDataRow2.Address);
}

// find and remove the new row using the


// FindByCustomerID() and RemoveCustomersRow() methods
// of myDataTable

myDataRow = myDataTable.FindByCustomerID("J5COM");
myDataTable.RemoveCustomersRow(myDataRow);

// push the delete to the database

sqlDataAdapter1.Update(myDataTable);

sqlConnection1.Close();
}

7.14. Kết chương

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 đó.

Những nội dung chính được đề cập trong chương này


 Lớp DataView
 Tạo và sử dụng đối tượng DataView
 Sử dụng thuật toán sắp xếp (mặc định)
 Chức năng lọc nâng cao
 Lớp DataRowView
 Tìm các đối tượng DataRowView trong DataView
 Thêm, cập nhật và xóa các DataRowView từ đối tượng DataView
 Tạo các đối tượng DataView con
 Lớp DataViewManager
 Tạo và sử dụng đối tượng DataViewManager
 Tạo một DataView dùng Visual Studio .Net

8.1. Lớp DataView


Đối tượng DataView được dùng để xem nội dung các dòng trong một DataTable qua
một bộ lọc. Ta cũng có thể dùng DataView để sắp xếp các dòng hoặc thêm dòng mới,
cập nhật và xóa các dòng khỏi một DataView. Các thay đổi dữ liệu trên DataView cũng
được cập nhật cho DataRow trong DataTable mà nó đọc dữ liệu.
Các bảng sau liệt kê một số thuộc tính, phương thức của lớp DataView.
PROPERTY TYPE DESCRIPTION
AllowDelete bool Thuộc tính đọc và ghi. Cho biết bạn có quyền
xóa các đối tượng DataRowView objects khỏi
DataView hay không. Mặc định là true.
AllowEdit bool Thuộc tính đọc và ghi. Cho biết bạn có quyền

Trang 293
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


thay đổi giá trị trên các đối tượng
DataRowView của DataView hay không. Mặc định
là true.
AllowNew bool Thuộc tính đọc và ghi. Cho biết bạn có quyền
thêm các đối tượng DataRowView mới vào
DataView hay không. Mặc định là true.
ApplyDefaultSort bool Thuộc tính đọc và ghi. Cho DataView biết
việc sắp xếp sẽ dùng thuật toán sắp xếp mặc
định. Khi được gán là true, thuật toán sắp
xếp mặc định được dùng và sắp tăng theo dữ
liệu trên thuộc tính PrimaryKey (nếu được
thiết lập) của DataTable bên dưới DataView.
Giá trị mặc định là false.
Count int Thuộc tính chỉ đọc. Lấy số dòng có trong
DataView.
DataViewManager DataViewManager Thuộc tính chỉ đọc. Lấy đối tượng
DataViewManager liên quan tới DataView.
RowFilter string Thuộc tính đọc và ghi. Lấy hay gán biểu thức
điều kiện để trích lọc các dòng từ DataView.
RowStateFilter DataViewRowState Thuộc tính đọc và ghi. Lấy hay gán biểu thức
điều kiện để trích lọc các dòng dựa trên các
hằng số từ enum DataViewRowState. Giá trị
mặc định là CurrentRows. Các giá trị trong
enum này được liệt kê trong bảng 8.3
Sort string Thuộc tính đọc và ghi. Lấy hay gán biểu thức
chứa các cột và thứ tự cần sắp xếp của các
dòng trong DataView. Biểu thức này chứa tên
của các cột, theo sau bởi các từ khóa ASC
(ascending – sắp tăng) hoặc DESC (descending
– sắp giảm). Mặc định, giá trị trong một cột
được sắp theo thứ tự tăng. Bạn có thể sắp
xếp trên nhiều cột, các cột tác nhau bởi dấu
phẩy. Ví dụ: CustomerID ASC, CompanyName
DESC.
Table DataTable Thuộc tính đọc và ghi. Lấy hay gán DataTable
chứa trong DataView.
Bảng 8.1. Các thuộc tính trong lớp DataView

METHOD RETURN DESCRIPTION


TYPE
AddNew() DataViewRow Thêm một DataRowView vào DataView, đồng thời
thêm một DataRow mới vào DataTable.
BeginInit() void Bắt đầu việc khởi tạo DataView trong lúc thực
thi trên Form hoặc Component.
CopyTo() void Sao chép các dòng từ DataView vào một mảng.
Phương thức này chỉ dùng cho các giao diện Web
Forms.
Delete() void Xóa DataRowView khỏi DataView theo chỉ số. Việc
xóa DataRow bên dưới sẽ không có hiệu lực cho
tới khi hàm AcceptChanges()của DataTable được
gọi. Bạn cũng có thể hủy bỏ việc xóa bằng cách
gọi phương thức RejectChanges() của DataTable.
EndInit() void Kết thúc việc khởi tạo DataView trên Form hoặc

Trang 294
Giáo trình lập trình cơ sở dữ liệu

METHOD RETURN DESCRIPTION


TYPE
Component trong lúc thực thi.
Find() int Phương thức có nhiều dạng. Tìm theo giá trị khóa
chính và trả về chỉ số của DataRowView trong
DataView. Nếu không tìm thấy, trả về -1. Đầu
tiên, bạn phải gán thuộc tính Sort của DataView
để sắp xếp dữ liệu trên cột khóa chính. Ví dụ:
để tìm một DataRowView dựa trên CustomerID, bạn
phải sắp xếp cột CustomerID theo thứ tự tăng
hoặc giảm như sau (CustomerID ASC hoặc
CustomerID DESC).
FindRows() DataRowView[] Phương thức có nhiều dạng. Tìm và trả về một
mảng các DataRowView theo một giá trị khóa chính
cụ thể. Giống với phương thức Find(), bạn phải
gán giá trị cho thuộc tính Sort của DataView để
sắp xếp giá trị trên cột khóa chính.
GetEnumerator() IEnumerator Trả về một bảng liệt kê (Enumerator) cho
DataView.
ToString() string Trả về chuỗi biểu diễn đối tượng DataView.
Bảng 8.2. Một số phương thức của DataView

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.

8.2. Tạo và sử dụng đối tượng DataView


Để tạo một đối tượng DataView, ta dùng một trong các phương thức khởi tạo sau
DataView()
DataView(DataTable myDataTable)
DataView(DataTable myDataTable, string filterExpression,
string sortExpression, DataViewRowState rowState)

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.

DataView customersDV = new DataView();


customersDV.Table = customersDT;
customersDV.RowFilter = filterExpression;
customersDV.Sort = sortExpression;
customersDV.RowStateFilter = 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.

Chương trình 8.1: Tạo và sử dụng DataView


/*
UsingDataView.cs illustrates the use of a DataView object to
filter and sort rows
*/

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Country " +
"FROM Customers";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();

DataTable customersDT = myDataSet.Tables["Customers"];

// set up the filter and sort expressions

string filterExpression = "Country = 'UK'";


string sortExpression = "CustomerID ASC, CompanyName DESC";
DataViewRowState rowStateFilter = DataViewRowState.OriginalRows;

// create a DataView object named customersDV

DataView customersDV = new DataView();


customersDV.Table = customersDT;
customersDV.RowFilter = filterExpression;
customersDV.Sort = sortExpression;
customersDV.RowStateFilter = rowStateFilter;

// display the rows in the customersDV DataView object

Trang 297
Giáo trình lập trình cơ sở dữ liệu

foreach (DataRowView myDataRowView in customersDV)


{
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(myDataRowView[count]);
}
Console.WriteLine("");
}
}
}

Kết quả chạy chương trình


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

8.3. Sử dụng thuật toán sắp xếp mặc định


Nếu muốn sắp xếp các đối tượng DataRowView trong DataView dựa trên khóa
chính của DataTable, ta có một giải pháp khá nhanh. Thay vì gán giá trị cho thuộc tính
Sort của DataView, ta thiết lập giá trị cho thuộc tính PrimaryKey của DataTable sau đó
gán giá trị true cho thuộc tính ApplyDefaultSort của DataView.
Khi đó, thuộc tính Sort của DataView tự động được gán bằng khóa chính của
DataTable. Điều này làm cho các đối tượng DataRowView trong DataView được sắp
xếp tăng dần theo giá trị trên cột khóa chính.
Ví dụ:
Đoạn mã sau thiết lập giá trị cho thuộc tính PrimaryKey của DataTable có tên
customersDT. Sau đó, gán giá trị true cho thuộc tính ApplyDefaultSort của DataView.
Trang 298
Giáo trình lập trình cơ sở dữ liệu

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.

8.4. Trích lọc nâng cao


Thuộc tính RowFilter của DataView tương tự như mệnh đề WHERE trong lệnh
SELECT. Do đó, nó có tính năng rất mạnh trong việc thiết lập các biểu thích điều kiện
để trích lọc từ DataView.
Chẳng hạn, 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, ký tự đại diện (* và %) và cả các hàm tính toán.
Ví dụ sau minh họa cách sử dụng toán tử LIKE kết hợp với ký tự đại diện (%) để lọc
ra các dòng có giá trị trên cột CustomerName bắt đầu bởi Fr.

string filterExpression = "CompanyName LIKE 'Fr%'";


customersDV.RowFilter = filterExpression;

8.5. Lớp DataRowView


Các dòng trong đối tượng DataView được lưu bởi các đối tượng DataRowView. Một
DataRowView cho phép truy xuất đến đối tượng DataRow bên dưới DataTable. Khi
xem xét hay thay đổi nội dung của DataRowView, thực chất, chúng ta đang làm việc
với các DataRow nằm bên dưới. Cần lưu ý điều này khi làm việc với các đối tượng
DataRowView.
Các bảng sau liệt kê một số phương thức và thuộc tính của lớp DataRowView.

PROPERTY TYPE DESCRIPTION


DataView DataView Thuộc tính chỉ đọc. Lấy đối tượng DataView chứa
DataRowView.
IsEdit bool Thuộc tính chỉ đọc. Cho biết có phải DataRowView (và
cả DataRow bên dưới) đang được cập nhật.
IsNew bool Thuộc tính chỉ đọc. Cho biết có phải DataRowView vừa
được thêm vào.
Row DataRow Thuộc tính chỉ đọc. Lấy đối tượng DataRow đang được
chứa trong DataRowView.

Trang 299
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


RowVersion DataRowVersion Thuộc tính chỉ đọc. Lấy đối tượng DataRowVersion của
DataRow bên dưới. Thuộc tính này có kiễu enum
System.Data.DataRowVersion và nhận một trong các giá
trị sau:

 Current: DataRow chứa các giá trị hiện tại.


 Default: DataRow chứa các giá trị mặc định.
 Original: DataRow chứa các giá trị ban đầu.
 Proposed: DataRow chứa các giá trị thay đổi.

Bảng 8.4. Các thuộc tính của DataRowView

METHOD RETURN DESCRIPTION


TYPE
BeginEdit() void Bắt đầu cập nhật DataRowView trong DataView và kéo
theo việc bắt đầu cập nhật DataRow bên dưới
DataTable. Sau đó, thay đổi DataRow này qua
DataRowView.
CancelEdit() void Hủy bỏ việc cập nhật DataRowView trong DataView,
cũng như việc thay đổi DataRow bên dưới.
CreateChildView() DataView Phương thức có nhiều dạng. Trả về DataView của
DataTable con (nếu có).
Delete() void Xóa các DataRowView trong DataView. Việc xóa các
DataRow bên dưới sẽ không được xác nhận cho tới
khi phương thức AcceptChanges() của DataTable được
gọi. Bạn có thể hủy bỏ việc xóa bằng cách gọi
phương thức RejectChanges() của DataTable. phương
thức này cũng hủy bỏ các dòng mới thêm hoặc sửa
đổi nhưng chưa được xác nhận.
EndEdit() void Kết thúc việc cập nhật một DataRowView.
Bảng 8.5. Các phương thức của DataRowView

8.5.1. Tìm kiếm các DataRowView trong DataView


Phương thức Find() của DataView được dùng để cho biết chỉ số của một
DataRowView trong DataView đó dựa vào giá trị của cột khóa chính. Phương thức này
trả về số nguyên cho biết chỉ số của DataRowView nếu được tìm thấy, ngược lại, trả về
-1.
Ngoài ra, ta 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.
8.5.2. Tìm chỉ số của một DataRowView dùng phương thức Find
Để tìm chính xác chỉ số của DataRowView, trước hết, ta phải gán giá trị cho thuộc
tính Sort của DataView để sắp xếp giá trị trên cột khóa chính.
Ví dụ:
Để tìm một DataRowView dựa trên cột CustomerID, bạn phải đặt giá trị thuộc tính
Set của DataView là CustomerID, CustomerID ASC hoặc CustomerID DESC.

Trang 300
Giáo trình lập trình cơ sở dữ liệu

string sortExpression = "CustomerID";


customersDV.Sort = sortExpression;

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Country " +
"FROM Customers";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();

DataTable customersDT = myDataSet.Tables["Customers"];

// set up the filter and sort expressions

string filterExpression = "Country = 'UK'";


string sortExpression = "CustomerID";
DataViewRowState rowStateFilter = DataViewRowState.OriginalRows;

// create a DataView object named customersDV

DataView customersDV = new DataView();


customersDV.Table = customersDT;
customersDV.RowFilter = filterExpression;
customersDV.Sort = sortExpression;
customersDV.RowStateFilter = rowStateFilter;

// display the rows in the customersDV DataView object

foreach (DataRowView myDataRowView in customersDV)


{
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(myDataRowView[count]);
}
Console.WriteLine("");
}

// use the Find() method of customersDV to find the index of


// the DataRowView whose CustomerID is BSBEV

int index = customersDV.Find("BSBEV");

Console.WriteLine("BSBEV found at index " + index + "\n");

Trang 302
Giáo trình lập trình cơ sở dữ liệu

// use the FindRows() method of customersDV to find the


// DataRowView whose CustomerID is BSBEV

DataRowView[] customersDRVs = customersDV.FindRows("BSBEV");

foreach (DataRowView myDataRowView in customersDRVs)


{
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(myDataRowView[count]);
}
Console.WriteLine("");
}
}
}

Kết quả chạy chương trình


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

BSBEV found at index 1


BSBEV, B's Beverages, UK

8.5.4. Thêm, cập nhật và xóa các DataRowView từ DataView


Một điều quan trọng cần nhớ là các đối tượng DataRowView trong DataView cho
phép truy xuất đến các đối tượng DataRow bên dưới trong một DataTable. Vì thế, khi
lấy hay thay đổi nội dung của một DataRowView, thực chất, ta đang làm việc với
DataRow bên dưới. Tương tự, việc xóa một DataRowView cũng có nghĩa là bạn đã xóa
DataRow bên dưới.

8.5.4.1. Thêm một DataRowView vào DataView


Để thêm một DataRowView mới vào DataView, ta gọi phương thức AddNew() của
DataView đó. Phương thức này trả về đối tượng DataRowView mà bạn đã gán đầy đủ
các giá trị cho dòng mới.
Ví dụ:
DataRowView customerDRV = customersDV.AddNew();
customerDRV["CustomerID"] = "J7COM";
customerDRV["CompanyName"] = "J7 Company";
customerDRV["Country"] = "UK";
customerDRV.EndEdit();

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:

DataRow customerDR = customerDRV.Row;

8.5.4.2. Cập nhật một DataRowView đang tồn tại


Để bắt đầu việc cập nhật một DataRowView đang tồn tại trong DataView, ta gọi
phương thức BeginEdit() của DataRowView đó trong DataView. Sau đó, gán giá trị trên
các DataColumn cho DataRow bên dưới qua đối tượng DataRowView. Sau khi hoàn tất
việc cập nhật, ta gọi phương thức EndEdit() để xác nhận các thay đổi vừa tạo ra trong
DataTable bên dưới.
Ví dụ:
customersDV[0].BeginEdit();
customersDV[0]["CompanyName"] = "Widgets Inc.";
customersDV[0].EndEdit();

8.5.4.3. Xóa một DataRowView đang tồn tại


Phương thức Delete() được xây dựng cho cả DataView và DataRowView để xóa
một DataRowView đang tồn tại trong DataView. Khi gọi phương thức Delete() của
DataView, ta cần phải đưa ra chỉ số của DataRowView muốn xóa. Còn phương thức
Delete() của đối tượng DataRowView không có tham số.
Cho dù sử dụng phương thức nào đi nữa thì việc xóa một DataRowView chỉ được
xác nhận và lưu các thay đổi vào DataTable bên dưới khi phương thức AcceptChanges()
của DataTable này được gọi.
Ví dụ:
// Gọi phương thức Delete từ DataView
customersDV.Delete(1);

// Gọi phương thức Delete từ DataRowView


customersDV[2].Delete();

// Xác nhận các thay đổi


customersDT.AcceptChanges();

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.

Chương trình 8.3: Thêm, cập nhật và xóa các DataRowView


/*
AddModifyAndRemoveDataRowViews.cs illustrates how to
add, modify, and remove DataRowView objects from a DataView
*/

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]);
}
}

public static void Main()


{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
SqlCommand mySqlCommand = mySqlConnection.CreateCommand();
mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Country " +
"FROM Customers";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();

DataTable customersDT = myDataSet.Tables["Customers"];

// set up the filter expression

string filterExpression = "Country = 'UK'";

// create a DataView object named customersDV

DataView customersDV = new DataView();


customersDV.Table = customersDT;
customersDV.RowFilter = filterExpression;

// add a new DataRowView (adds a DataRow to the DataTable)

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"]);

Console.WriteLine("customerDRV.IsNew = " + customerDRV.IsNew);


Console.WriteLine("customerDRV.IsEdit = " + customerDRV.IsEdit);

customerDRV.EndEdit();

// get and display the underlying DataRow

DataRow customerDR = customerDRV.Row;


DisplayDataRow(customerDR, customersDT);

// modify the CompanyName of customerDRV


Console.WriteLine("\nSetting customersDV[0][\"
CompanyName\"] to Widgets Inc.");

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();

// display the underlying DataRow

DisplayDataRow(customersDV[0].Row, customersDT);

// remove the second DataRowView from customersDV


Console.WriteLine("\ncustomersDV[1][\"CustomerID\"] = " +
customersDV[1]["CustomerID"]);
Console.WriteLine("\nCalling customersDV.Delete(1)");

customersDV.Delete(1);

Console.WriteLine("customersDV[1].IsNew = " +
customersDV[1].IsNew);

Console.WriteLine("customersDV[1].IsEdit = " +
customersDV[1].IsEdit);

// remove the third DataRowView from customersDV

Console.WriteLine("\ncustomersDV[2][\"CustomerID\"] = " +
customersDV[2]["CustomerID"]);
Console.WriteLine("\nCalling customersDV[2].Delete()");

customersDV[2].Delete();

// call the AcceptChanges() method of customersDT to

Trang 306
Giáo trình lập trình cơ sở dữ liệu

// make the deletes permanent in customersDT

customersDT.AcceptChanges();

// display the rows in the customersDV DataView object

Console.WriteLine("\nDataRowView objects in customersDV:\n");


foreach (DataRowView myDataRowView in customersDV)
{
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(myDataRowView[count]);
}
Console.WriteLine("");
}
}
}

Kết quả chạy chương trình


Calling customersDV.AddNew()
customerDRV["CustomerID"] = J7COM
customerDRV["CompanyName"] = J7 Company
customerDRV["Country"] = UK
customerDRV.IsNew = True
customerDRV.IsEdit = True

In DisplayDataRow()
CustomerID = J7COM
CompanyName = J7 Company
Country = UK

Setting customersDV[0]["CompanyName"] to Widgets Inc.


customersDV[0]["CustomerID"] = AROUT
customersDV[0]["CompanyName"] = Widgets Inc.
customersDV[0].IsNew = False
customersDV[0].IsEdit = True

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()

DataRowView objects in customersDV:

AROUT, Widgets Inc., UK


CONSH, Consolidated Holdings, UK
ISLAT, Island Trading, UK

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

8.5.5. Tạo đối tượng DataView con


Trong trường hợp các DataTable có quan hệ cha con, bạn có thể dùng phương thức
CreateChildView() để tạo một DataView con của một DataRowView nằm trong
DataView chứa DataTable cha. Để gọi phương thức CreateChildView(), trước hết, ta
phải thêm một đối tượng DataRelation vào DataSet để định nghĩa một quan hệ giữa hai
DataTable.
Ví dụ:
Giả sử bạn có hai đối tượng DataTable có tên là customersDT, ordersDT và đưa một
đối tượng DataRelation vào DataSet như sau:
DataRelation customersOrdersDataRel =
new DataRelation( "CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);
myDataSet.Relations.Add( customersOrdersDataRel );

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;";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();

myDataSet.Tables["Table"].TableName = "Customers";
myDataSet.Tables["Table1"].TableName = "Orders";

DataTable customersDT = myDataSet.Tables["Customers"];


DataTable ordersDT = myDataSet.Tables["Orders"];

// add a DataRelation object to myDataSet

DataRelation customersOrdersDataRel =
new DataRelation( "CustomersOrders",
customersDT.Columns["CustomerID"],
ordersDT.Columns["CustomerID"]
);

myDataSet.Relations.Add( customersOrdersDataRel );

// create a DataView object named customersDV

DataView customersDV = new DataView();


customersDV.Table = customersDT;
customersDV.RowFilter = "Country = 'UK'";
customersDV.Sort = "CustomerID";

// display the first row in the customersDV DataView object

Console.WriteLine("Customer:");
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(customersDV[0][count]);
}

// create a child DataView named ordersDV that views


// the child rows for the first customer in customersDV

DataView ordersDV =
customersDV[0].CreateChildView("CustomersOrders");

// display the child rows in the customersDV DataView object

Console.WriteLine("\nOrderID's of the orders

Trang 309
Giáo trình lập trình cơ sở dữ liệu

placed by this customer:");


foreach (DataRowView ordersDRV in ordersDV)
{
Console.WriteLine(ordersDRV["OrderID"]);
}
}
}
Kết quả chạy chương trình
Customer:
AROUT
Around the Horn
UK

OrderID's of the orders placed by this customer:


10355
10383
10453
10558
10707
10741
10743
10768
10793
10864
10920
10953
11016

8.6. Lớp DataViewManager


Lớp DataViewManager cho phép quản lý tập trung nhiều đối tượng DataView trong
một DataSet. Lớp này cũng cho phép tạo các đối tượng DataView dễ dàng trong lúc
chạy chương trình bằng cách dùng phương thức CreateDataView(). Phương thức này
nhận một tham số là DataTable sẽ chứa trong DataView mới. Kết quả trả về của hàm là
một đối tượng DataView.
Lớp DataViewManager có một sự kiện ListChanged phát sinh khi danh sách được
quản lý bởi một DataView trong DataViewManager thay đổi. Sự kiện này được quản lý
bởi trình xử lý sự kiện ListChangedEventHandler.

PROPERTY TYPE DESCRIPTION


DataSet DataSet Thuộc tính đọc và ghi. Lấy hay gán
DataSet được sử dụng bởi
DataViewManager.
DataViewSettings DataViewSettingCollection Thuộc tính chỉ đọc. Lấy đối tượng
DataViewSettingCollection cho mỗi
DataTable trong DataSet. Một
DataViewSettingCollection cho phép
truy xuất các thuộc tính của
DataView tương ứng với DataTable.
Bảng 8.6. Các thuộc tính của DataViewManager.

Trang 310
Giáo trình lập trình cơ sở dữ liệu

8.7. Tạo và sử dụng đối tượng DataViewManager


Để tạo một đối tượng DataViewManager, ta sử dụng một trong các phương thức
khởi tạo sau:
DataViewManager()
DataViewManager(DataSet myDataSet)
Trong đó:
 myDataSet: là đối tượng DataSet được sử dụng bởi DataViewManager. Giá trị
của tham số này sẽ được gán cho thuộc tính DataSet của đối tượng
DataViewManager.
Ví dụ:
Giả sử bạn có một DataSet với tên myDataSet chứa một DataTable để lấy dữ liệu từ
bảng Customers. Đoạn mã sau tạo một đối tượng DataViewManager có tên myDVM,
gửi myDataSet vào tham số của phương thức khởi tạo.

DataViewManager myDVM = new DataViewManager(myDataSet);

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.

Chương trình 8.4: Sử dụng đối tượng DataViewManager


/*
UsingDataViewManager.cs illustrates the use of a
DataViewManager object
*/

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"
);

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName, Country " +
"FROM Customers";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet, "Customers");
mySqlConnection.Close();

DataTable customersDT = myDataSet.Tables["Customers"];

// create a DataViewManager object named myDVM

DataViewManager myDVM = new DataViewManager(myDataSet);

// set the Sort and RowFilter properties for


// the Customers DataTable

myDVM.DataViewSettings["Customers"].Sort = "CustomerID";
myDVM.DataViewSettings["Customers"].RowFilter =
"Country = 'UK'";

// display the DataViewSettingCollectionString property of myDVM

Console.WriteLine("myDVM.DataViewSettingCollectionString = " +
myDVM.DataViewSettingCollectionString + "\n");

// call the CreateDataView() method of myDVM to create a


// DataView named customersDV for the customersDT DataTable

DataView customersDV = myDVM.CreateDataView(customersDT);

// display the rows in the customersDV DataView object

foreach (DataRowView myDataRowView in customersDV)


{
for (int count = 0; count <
customersDV.Table.Columns.Count; count++)
{
Console.WriteLine(myDataRowView[count]);
}
Console.WriteLine("");
}
}
}

Kết quả chạy chương trình


myDVM.DataViewSettingCollectionString =
<DataViewSettingCollectionString>
<Customers Sort="CustomerID" RowFilter="Country = 'UK'"

Trang 312
Giáo trình lập trình cơ sở dữ liệu

RowStateFilter="CurrentRows"/>
</DataViewSettingCollectionString>

AROUT, Widgets Inc., UK


CONSH, Consolidated Holdings, UK
ISLAT, Island Trading, UK
NORTS, North/South, UK
SEVES, Seven Seas Imports, UK
J7COM, J7 Company, UK

8.8. Tạo đối tượng DataView dùng Visual Studio .Net


Để tạo một đối tượng DataView bằng VS.Net, thực hiện theo các bước sau:
- Mở VS.Net và tạo một dự án Windows Application mới
- Tạo một DataGridView bằng cách kéo nó từ thẻ Data của Toolbox vào Form.
- Mở Server Explorer, kết nối tới cơ sở dữ liệu Northwind.
- Tạo một đối tượng SqlConnection và một đối tượng SqlDataAdapter để đổ dữ
liệu từ bảng Customers vào DataGridView.
- Nhấp phải chuột vào đối tượng SqlDataAdapter, chọn Generate DataSet. Giữ
nguyên các giá trị mặc định và nhấp OK để tạo DataSet.
- Kéo một đối tượng DataView từ thẻ Data của Toolbox vào Form. Bên dưới Form
xuất hiện đối tượng DataView có tên dataView1.
- Gán giá trị cho thuộc tính Table của DataView là dataSet21.Customers. Gán giá
trị cho RowFilter là Country=‘UK’, thuộc tính Sort là CustomerID.
- Đặt thuộc tính DataSource của dataGridView1 là dataView1.
- Nhấp phải chuột lên Form, chọn View Code. Trong phương thức khởi tạo
Form1(), thêm đoạn mã sau

Trang 313
Giáo trình lập trình cơ sở dữ liệu

Hình 8.1. Thiết lập thuộc tính cho DataView

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

// call the Fill() method of sqlDataAdapter1


// to populate dataSet11 with a DataTable named
// Customers
sqlDataAdapter1.Fill(dataSet11, "Customers");
}

Hình 8.2. Thiết lập thuộc tính cho DataGridView

Hình 8.3. Kết quả chạy chương trình.


Trang 314
Giáo trình lập trình cơ sở dữ liệu

8.9. Kết chương

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.

Bài tập chương 8

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.

Những nội dung chính


 Lớp UniqueConstraint
 Tạo một đối tượng UniqueConstraint
 Lớp ForeignKeyConstraint
 Tạo một đối tượng ForeignKeyConstraint
 Lớp DataRelation
 Tạo và sử dụng đối tượng DataRelation
 Thêm, cập nhật và xóa các dòng có quan hệ với nhau
 Các vấn đề khi cập nhật khóa chính

9.1. Lớp UniqueConstraint


Đối tượng UniqueConstraint dùng để bảo đảm giá trị trên một DataColumn hoặc
một tổ hợp giá trị trên các DataColumn nào đó xác định duy nhất một đối tượng
DataRow trong DataTable. Lớp UniqueConstraint được dẫn xuất (kế thừa) từ lớp
System.Data. Constraint. Bảng sau cho thấy một số thuộc tính của đối tượng
UniqueConstraint.

Trang 316
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


Columns DataColumn[] Lấy mảng các đối tượng DataColumn chứa
trong đối tượng UniqueConstraint.
ConstraintName String Lấy tên của đối tượng UniqueConstraint.
ExtendedProperties PropertyCollection Lấy đối tượng PropertyCollection mà bạn
dùng để chứa các chuỗi thông tin bổ sung
cho ràng buộc.
IsPrimaryKey bool Trả về giá trị bool cho biết đối tượng
UniqueConstraint được áp dụng trên khóa
chính.
Table DataTable Lấy DataTable được áp dụng đối tượng
UniqueConstraint.
Bảng 9.1. Một số thuộc tính của UniqueConstraint

9.2. Tạo đối tượng UniqueConstraint


Lớp UniqueConstraint có nhiều phương thức tạo lập với nhiều dạng khác nhau cho
phép tạo một đối tượng UniqueConstraint nhanh chóng.
UniqueConstraint(DataColumn myDataColumn)
UniqueConstraint(DataColumn[] myDataColumns)
UniqueConstraint(DataColumn myDataColumn, bool isPrimaryKey)
UniqueConstraint(DataColumn[] myDataColumns, bool isPrimaryKey)
UniqueConstraint(string constraintName, DataColumn myDataColumn)
UniqueConstraint(string constraintName, DataColumn[] myDataColumns)
UniqueConstraint(string constraintName,
DataColumn myDataColumn, bool isPrimaryKey)
UniqueConstraint(string constraintName,
DataColumn[] myDataColumns, bool isPrimaryKey)
UniqueConstraint(string constraintName,
string[] columnNames, bool isPrimaryKey)
Trong đó:
 myDataColumn và myDataColumns: là các đối tượng DataColumn mà bạn
muốn áp đặt ràng buộc về tính duy nhất.
 constraintName: là tên được gán cho thuộc tính ConstraintName của đối tượng
UniqueConstraint.
 isPrimaryKey: xác định đối tượng UniqueConstraint được áp đặt cho khóa chính.

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.

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'ALFKI'" +
"SELECT OrderID, CustomerID " +
"FROM Orders " +
"WHERE CustomerID = 'ALFKI';";

Trang 317
Giáo trình lập trình cơ sở dữ liệu

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();
mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();

myDataSet.Tables["Table"].TableName = "Customers";
myDataSet.Tables["Table1"].TableName = "Orders";

DataTable customersDT = myDataSet.Tables["Customers"];


DataTable ordersDT = myDataSet.Tables["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:");

foreach (DataColumn myDataColumn in myUC.Columns)


{
Console.WriteLine("" + myDataColumn);
}
Console.WriteLine("myUC.ConstraintName = " + myUC.ConstraintName);
Console.WriteLine("myUC.IsPrimaryKey = " + myUC.IsPrimaryKey);
Console.WriteLine("myUC.Table = " + myUC.Table);

Kết quả xuất ra trên màn hình

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.

9.3. Lớp ForeignKeyConstraint


Đối tượng ForeignKeyConstraint dùng để biểu diễn một ràng buộc khóa ngoại giữa
hai đối tượng DataTable. Ràng buộc này đảm bảo rằng mỗi DataRow trong DataTable
con phải phù hợp (hay có tương ứng) với một DataRow trong DataTable cha. Lớp
ForeignKeyConstraint cũng kế thừa từ lớp System.Data.Constraint. Bảng sau liệt kê một
số thuộc tính của lớp ForeignKeyConstraint.
PROPERTY TYPE DESCRIPTION
AcceptRejectRule AcceptRejectRule Thuộc tính đọc và ghi. Lấy hay gán đối
tượng AcceptRejectRule cho biết các quy
tắc xử lý khi phương thức AcceptChanges
của DataTable được gọi. Các giá trị của
enum System.Data.AcceptRejectRule:

 Cascade: chỉ ra rằng, các thay đổi


trên mỗi đối tượng DataRow trong
DataTable cha cũng có tác dụng
trên DataTable con.
 None: Không xử lý.

Giá trị mặc định là None.


Columns DataColumn[] Thuộc tính chỉ đọc. Lấy mảng các đối
tượng DataColumn từ DataTable con.
ConstraintName string Lấy tên của đối tượng UniqueConstraint.
DeleteRule Rule Thuộc tính đọc và ghi. Lấy hay gán các
quy tắc (Rule) xác định cách xử lý khi
một DataRow trong DataTable cha bị xóa.
Các giá trị của enum System.Data.Rule:

 Cascade: chỉ ra rằng việc xóa hay


cập nhật các đối tượng DataRow
trong DataTable cha cũng có tác
dụng lên DataTable con.
 None: Không xử lý.
 SetDefault: Chỉ ra rằng giá trị
trên DataColumn của DataTable con
được gán giá trị bằng với giá trị
trong thuộc tính DefaultValue của
DataColumn đó.
 SetNull: chỉ ra rằng giá trị của
DataColumn trong DataTable con
được gán giá trị DBNull.

Giá trị mặc định là Cascade.


ExtendedProperties PropertyCollection Thuộc tính chỉ đọc. Lấy đối tượng
PropertyCollection dùng để lưu trữ các
chuỗi chứa thông tin bổ sung.
RelatedColumns DataColumn[] Thuộc tính chỉ đọc. Lấy mảng các đối

Trang 319
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


tượng DataColumn trong bảng cha được áp
dụng ràng buộc UniqueConstraint.
RelatedTable DataTable Thuộc tính chỉ đọc. Lấy DataTable cha áp
dụng ràng buộc UniqueConstraint.
Table DataTable Thuộc tính chỉ đọc. Lấy DataTable con áp
dụng ràng buộc UniqueConstraint.
UpdateRule Rule Thuộc tính đọc và ghi. Lấy hay gán các
quy tắc (Rule) chỉ ra cách xử lý khi một
DataRow trong DataTable cha bị thay đổi.

Giá trị mặc định là Cascade.


Bảng 9.2. Một số thuộc tính của ForeignKeyConstraint

9.4. Tạo đối tượng ForeignKeyConstraint


Để tạo một ràng buộc khóa ngoại ForeignKeyConstraint, ta sử dụng một trong các
phương thức tạo lập sau:
ForeignKeyConstraint(DataColumn parentDataColumn,
DataColumn childDataColumn)

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);
}

Console.WriteLine("myFKC.ConstraintName = " + myFKC.ConstraintName);


Console.WriteLine("myFKC.DeleteRule = " + myFKC.DeleteRule);
Console.WriteLine("RelatedColumns:");

foreach (DataColumn relatedDataColumn in myFKC.RelatedColumns)


{
Console.WriteLine("" + relatedDataColumn);
}

Console.WriteLine("myFKC.RelatedTable = " + myFKC.RelatedTable);


Console.WriteLine("myFKC.Table = " + myFKC.Table);
Console.WriteLine("myFKC.UpdateRule = " + myFKC.UpdateRule);

Kết quả hiển thị lên màn hình


myFKC.AcceptRejectRule = None
Columns:
CustomerID
myFKC.ConstraintName = ForeignKeyConstraintCustomersOrders
myFKC.DeleteRule = Cascade
RelatedColumns:
CustomerID
myFKC.RelatedTable = Customers
myFKC.Table = Orders
myFKC.UpdateRule = Cascade

Trang 321
Giáo trình lập trình cơ sở dữ liệu

9.5. Lớp DataRelation


Đối tượng DataRelation dùng để biểu diễn một quan hệ giữa hai đối tượng
DataTable. Một đối tượng DataRelation tạo ra mô hình mối quan hệ cha con giữa hai
bảng trong cơ sở dữ liệu. Theo mặc định, khi tạo một đối tượng DataRelation, một đối
tượng UniqueConstraint và một đối tượng ForeignKeyConstraint sẽ tự động lần lượt
được thêm vào các đối tượng DataTable cha và DataTable con. Bảng sau liệt kê một số
thuộc tính của DataRelation.
PROPERTY TYPE DESCRIPTION
ChildColumns DataColumn[] Thuộc tính chỉ đọc. Lấy mảng các đối
tượng DataColumn trong DataTable con.
ChildKeyConstraint ForeignKeyConstraint Thuộc tính chỉ đọc. Lấy đối tượng
ForeignKeyConstraint liên quan đển
DataRelation.
ChildTable DataTable Thuộc tính chỉ đọc. Lấy đối tượng
DataTable con.
DataSet DataSet Thuộc tính chỉ đọc. Lấy DataSet chứa
đối tượng DataRelation.
ExtendedProperties PropertyCollection Thuộc tính chỉ đọc. Lấy đối tượng
PropertyCollection dùng để lưu trữ
các chuỗi chứa thông tin bổ sung.
Nested bool Thuộc tính đọc và ghi. Lấy hay gán
giá trị bool cho biết các đối tượng
DataRelation có thể lồng nhau
(nested). Điều này thật sự hữu ích
khi định nghĩa các quan hệ phân cấp
trong XML. Giá trị mặc định là false.
ParentColumns DataColumn[] Thuộc tính chỉ đọc. Lấy mảng các đối
tượng DataColumn trong DataTable cha.
ParentKeyConstraint UniqueConstraint Thuộc tính chỉ đọc. Lấy đối tượng
UniqueConstraint biểu diễn ràng buộc
nhằm đảm bảo các giá trị trong
DataColumn của DataTable cha là duy
nhất.
ParentTable DataTable Thuộc tính chỉ đọc. Lấy đối tượng
DataTable cha.
RelationName string Thuộc tính chỉ đọc. Lấy tên của đối
tượng DataRelation.
Bảng 9.3. Một số thuộc tính của DataRelation

9.6. Tạo và sử dụng đối tượng DataRelation


Phần này hướng dẫn cách tạo một đối tượng DataRelation để định nghĩa một quan
hệ giữa hai đối tượng DataTable. DataTable thứ nhất chứa một số dòng trong bảng
Customers và DataTable thứ hai chứa một số dòng trong bảng Orders. Như đã khảo sát
trong chương 1, cột CustomerID của bảng con Orders là khóa ngoại, liên kết đến cột
CustomerID của bảng cha Customers.

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" +
")";

SqlDataAdapter mySqlDataAdapter = new SqlDataAdapter();


mySqlDataAdapter.SelectCommand = mySqlCommand;

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

mySqlDataAdapter.Fill(myDataSet);
mySqlConnection.Close();

myDataSet.Tables["Table"].TableName = "Customers";
myDataSet.Tables["Table1"].TableName = "Orders";

DataTable customersDT = myDataSet.Tables["Customers"];


DataTable ordersDT = myDataSet.Tables["Orders"];

9.6.1. Tạo đối tượng DataRelation


Tiếp theo, ta định nghĩa một quan hệ giữa hai DataTable customersDT và ordersDT.
Việc tạo đối tượng DataRelation được thực hiện bằng cách dùng một trong các phương
thức tạo lập sau:
DataRelation(string dataRelationName,
DataColumn parentDataColumn,
DataColumn childDataColumn)

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.

9.6.2. Khảo sát các ràng buộc tạo bởi DataRelation


Theo mặc định, khi tạo một đối tượng DataRelation, một ràng buộc
UniqueConstraint và một ràng buộc ForeignKeyConstraint sẽ tự động được thêm vào
các đối tượng DataTable cha và DataTable con. Ta có thể lấy đối tượng
UniqueConstraint từ DataRelation thông qua thuộc tính ParentKeyConstraint. Chẳng
hạn:
UniqueConstraint myUC =
customersOrdersDataRel.ParentKeyConstraint;

Đ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);
}

Console.WriteLine("myUC.ConstraintName = " + myUC.ConstraintName);


Console.WriteLine("myUC.IsPrimaryKey = " + myUC.IsPrimaryKey);
Console.WriteLine("myUC.Table = " + myUC.Table);

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:");

foreach (DataColumn myDataColumn in myFKC.Columns)


{
Console.WriteLine("" + myDataColumn);
}

Console.WriteLine("myFKC.ConstraintName = " + myFKC.ConstraintName);


Console.WriteLine("myFKC.DeleteRule = " + myFKC.DeleteRule);
Console.WriteLine("RelatedColumns:");

foreach (DataColumn relatedDataColumn in myFKC.RelatedColumns)


{
Console.WriteLine("" + relatedDataColumn);
}

Console.WriteLine("myFKC.RelatedTable = " + myFKC.RelatedTable);


Console.WriteLine("myFKC.Table = " + myFKC.Table);
Console.WriteLine("myFKC.UpdateRule = " + myFKC.UpdateRule);

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.

9.7.1. Phương thức GetChildRow


Phương thức này được dùng để lấy các đối tượng DataRow có liên quan nằm trong
DataTable con từ một DataRow trong DataTable cha.
Chẳng hạn, đoạn mã sau hiển thị các đối tượng DataRow cha nằm trong DataTable
customersDT và các đối tượng DataRow con có liên quan lấy từ DataTable ordersDT.
foreach (DataRow customerDR in customersDT.Rows)
{
Console.WriteLine("\nCustomerID = " + customerDR["CustomerID"]);
Console.WriteLine("CompanyName = " + customerDR["CompanyName"]);

DataRow[] ordersDRs =
customerDR.GetChildRows("CustomersOrders");
Console.WriteLine("This customer placed the following orders:");

foreach (DataRow orderDR in ordersDRs)


{
Console.WriteLine("OrderID = " + orderDR["OrderID"]);
}
}

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

9.7.2. Phương thức GetParentRow


Phương thức này dùng để lấy DataRow cha từ một đối tượng DataRow con. Chẳng
hạn, đoạn mã sau hiển thị một DataRow con trong DataTable ordersDT và DataRow
cha lấy từ DataTable customersDT.
DataRow parentCustomerDR =
ordersDT.Rows[0].GetParentRow("CustomersOrders");

Trang 327
Giáo trình lập trình cơ sở dữ liệu

Console.WriteLine("\nOrder with OrderID of " +


ordersDT.Rows[0]["OrderID"] +
" was placed by the following customer:");

Console.WriteLine("CustomerID = " + parentCustomerDR["CustomerID"]);

Kết quả

Order with OrderID of 10643 was placed by the following customer:


CustomerID = ALFKI

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.

Chương trình 9.1: Tạo và thiết lập DataAdapter customersDA

public static void SetupCustomersDA(


SqlDataAdapter customersDA, SqlConnection mySqlConnection)
{

// SqlDataAdapter customersDA = new SqlDataAdapter();


// create a SqlCommand object to hold the SELECT

SqlCommand customersSelectCommand = mySqlConnection.CreateCommand();


customersSelectCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers";

// create a SqlCommand object to hold the INSERT

SqlCommand customersInsertCommand = mySqlConnection.CreateCommand();


customersInsertCommand.CommandText =
"INSERT INTO Customers (" +
" CustomerID, CompanyName " +
") VALUES (" +
" @CustomerID, @CompanyName" +
")";

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");

// create a SqlCommand object to hold the UPDATE

SqlCommand customersUpdateCommand = mySqlConnection.CreateCommand();


customersUpdateCommand.CommandText =
"UPDATE Customers " +
"SET " +
" CompanyName = @NewCompanyName " +
"WHERE CustomerID = @OldCustomerID " +
"AND CompanyName = @OldCompanyName";

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;

// create a SqlCommand object to hold the DELETE

SqlCommand customersDeleteCommand = mySqlConnection.CreateCommand();


customersDeleteCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = @OldCustomerID " +
"AND CompanyName = @OldCompanyName";

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;

// set the customersDA properties


// to the SqlCommand objects previously created
customersDA.SelectCommand = customersSelectCommand;
customersDA.InsertCommand = customersInsertCommand;
customersDA.UpdateCommand = customersUpdateCommand;
customersDA.DeleteCommand = customersDeleteCommand;

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.

Chương trình 9.2: Tạo và thiết lập DataAdapter ordersDA

public static void SetupOrdersDA(


SqlDataAdapter ordersDA, SqlConnection mySqlConnection )
{
Console.WriteLine("\nIn SetupOrdersDA()");

// create a SqlCommand object to hold the SELECT


SqlCommand ordersSelectCommand =
mySqlConnection.CreateCommand();
ordersSelectCommand.CommandText =
"SELECT OrderID, CustomerID, ShipCountry " +
"FROM Orders";

// create a SqlCommand object to hold the INSERT


SqlCommand ordersInsertCommand =
mySqlConnection.CreateCommand();
ordersInsertCommand.CommandText =
"INSERT INTO Orders (" +
" CustomerID, ShipCountry " +
") VALUES (" +
" @CustomerID, @ShipCountry" +
");" +
"SELECT @OrderID = SCOPE_IDENTITY();";

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;

// create a SqlCommand object to hold the UPDATE


SqlCommand ordersUpdateCommand =
mySqlConnection.CreateCommand();
ordersUpdateCommand.CommandText =
"UPDATE Orders " +
"SET " +

Trang 330
Giáo trình lập trình cơ sở dữ liệu

" ShipCountry = @NewShipCountry " +


"WHERE OrderID = @OldOrderID " +
"AND CustomerID = @OldCustomerID " +
"AND ShipCountry = @OldShipCountry";

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;

// create a SqlCommand object to hold the DELETE


SqlCommand ordersDeleteCommand =
mySqlConnection.CreateCommand();
ordersDeleteCommand.CommandText =
"DELETE FROM Orders " +
"WHERE OrderID = @OldOrderID " +
"AND CustomerID = @OldCustomerID " +
"AND ShipCountry = @OldShipCountry";

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;

// set the ordersDA properties


// to the SqlCommand objects previously created
ordersDA.SelectCommand = ordersSelectCommand;
ordersDA.InsertCommand = ordersInsertCommand;
ordersDA.UpdateCommand = ordersUpdateCommand;
ordersDA.DeleteCommand = ordersDeleteCommand;
}

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.

DataSet myDataSet = new DataSet();


mySqlConnection.Open();

customersDA.Fill(myDataSet, "Customers");
ordersDA.Fill(myDataSet, "Orders");

mySqlConnection.Close();

DataTable customersDT = myDataSet.Tables["Customers"];


DataTable ordersDT = myDataSet.Tables["Orders"];

Để 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

9.8.1. Thêm các đối tượng DataRow


Đoạn mã sau thêm một DataRow có tên cutomerDR vào DataTable customersDT,
giá trị trên cột CustomerID được gán là J6COM.
DataRow customerDR = customersDT.NewRow();
customerDR["CustomerID"] = "J6COM";
customerDR["CompanyName"] = "J6 Company";
customersDT.Rows.Add(customerDR);

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.

DataRow orderDR = ordersDT.NewRow();


orderDR["CustomerID"] = "J6COM";
orderDR["ShipCountry"] = "England";
ordersDT.Rows.Add(orderDR);

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.

9.8.2. Cập nhật và xóa đối tượng DataRow


Đoạn mã sau minh họa cách cập nhật giá trị trên cột CompanyName của DataRow
customerDR thành “Widget Inc” và thay đổi giá trị trên cột ShipCountry của DataRow
orderDR thành “USA”.
customerDR["CompanyName"] = "Widgets Inc.";
orderDR["ShipCountry"] = "USA";

Để xóa DataRow customerDR khỏi DataTable customersDT, ta viết:

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

9.8.3. Cập nhật các thay đổi vào cơ sở dữ liệu


Trong phần trước, ta đã thêm, cập nhật và xóa một số dòng trong hai DataTable
customersDT và ordersDT. Tiếp theo, ta sẽ tìm hiểu cách cập nhật các thay đổi này vào
các bảng tương ứng trong cơ sở dữ liệu. Khi thực hiện điều này, điều quan trọng cần lưu
ý là phải cập nhật các thay đổi theo một thứ tự nhất định để thỏa mãn các ràng buộc
khóa ngoại trong các bảng có quan hệ với nhau.
Chẳng hạn, trước khi thêm một dòng mới vào bảng Orders với giá trị trên cột
CustomerID là J6COM thì phải tồn tại một dòng trong bảng Customers với giá trị trên
cột CustomerID cũng là J6COM. Tương tự, ta cũng không thể xóa dòng trong bảng
Customers có giá trị trên cột CustomerID là J6COM khi vẫn còn có một số dòng trong
bảng Orders với cột CustomerID có cùng giá trị (tức là có quan hệ với nhau).
Để cập nhật các thay đổi được tạo ra trong các đối tượng DataTable ở ví dụ trên vào
các bảng tương ứng trong cơ sở dữ liệu, thực hiện lần lượt theo các bước sau:
- Đưa các đối tượng DataRow đã thêm từ customersDT vào bảng Customers
- Đưa các đối tượng DataRow đã thêm từ ordersDT vào bảng Orders
- Đưa các đối tượng DataRow đã cập nhật trong customersDT vào bảng Customers
- Đưa các đối tượng DataRow đã cập nhật trong ordersDT vào bảng Orders
- Xóa các đối tượng DataRow đã xóa khỏi ordersDT ra khỏi bảng Orders
- Xóa các đối tượng DataRow đã xóa khỏi customersDT ra khỏi bảng Customers

Để 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:

DataRow[] Select(string filterExpression, string sortExpression,


DataViewRowState myDataViewRowState)

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.

int numOfRows = customersDA.Update(newCustomersDRArray);

Đ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

public static void PushChangesToDatabase( SqlConnection mySqlConnection,


SqlDataAdapter customersDA, SqlDataAdapter ordersDA,
DataTable customersDT, DataTable ordersDT )
{
Console.WriteLine("\nIn PushChangesToDatabase()");
mySqlConnection.Open();

// push the new rows in customersDT to the database


Console.WriteLine("Pushing new rows in customersDT to database");

DataRow[] newCustomersDRArray =
customersDT.Select("", "", DataViewRowState.Added);

int numOfRows = customersDA.Update(newCustomersDRArray);


Console.WriteLine("numOfRows = " + numOfRows);

// push the new rows in ordersDT to the database


Console.WriteLine("Pushing new rows in ordersDT to database");

DataRow[] newOrdersDRArray =
ordersDT.Select("", "", DataViewRowState.Added);

numOfRows = ordersDA.Update(newOrdersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);

// push the modified rows in customersDT to the database


Console.WriteLine("Pushing modified rows in customersDT to database");

DataRow[] modifiedCustomersDRArray =
customersDT.Select("", "", DataViewRowState.ModifiedCurrent);

numOfRows = customersDA.Update(modifiedCustomersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);

// push the modified rows in ordersDT to the database


Console.WriteLine("Pushing modified rows in ordersDT to database");

DataRow[] modifiedOrdersDRArray =
ordersDT.Select("", "", DataViewRowState.ModifiedCurrent);

numOfRows = ordersDA.Update(modifiedOrdersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);

// push the deletes in ordersDT to the database

Trang 335
Giáo trình lập trình cơ sở dữ liệu

Console.WriteLine("Pushing deletes in ordersDT to database");

DataRow[] deletedOrdersDRArray =
ordersDT.Select("", "", DataViewRowState.Deleted);

numOfRows = ordersDA.Update(deletedOrdersDRArray);
Console.WriteLine("numOfRows = " + numOfRows);

// push the deletes in customersDT to the database


Console.WriteLine("Pushing deletes in customersDT to database");

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

Hình 9.1. Thẻ Relationships của FK_Orders_Customers

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.

9.9.3.1. Trường hợp thứ nhất


Giả sử mục Cascade Update Related Fields được chọn và thuộc tính UpdateRule
được gán là Cascade.
Nếu thay đổi giá trị của DataColumn CustomerID từ J6COM thành J7COM và cập
nhật các thay đổi vào cơ sở dữ liệu thì khi đó, các thay đổi được thực hiện thành công
trong các DataTable customersDT, ordersDT cũng được cập nhật thành công vào các
bảng Customers, Orders tương ứng.
Điều này thực hiện ngay cả khi bạn chỉ sử dụng cột OrderID trong mệnh đề WHERE
của đối tượng Command trong thuộc tính UpdateCommand của DataAdapter. Chẳng
hạn:
ordersUpdateCommand.CommandText =
"UPDATE Orders " +
"SET " +
" CustomerID = @NewCustomerID " +
"WHERE OrderID = @OldOrderID";

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

9.9.3.2. Trường hợp thứ 2


Giả sử tùy chọn Cascade Update Related Fields không được chọn và thuộc tính
UpdateRule được gán là Cascade.
Ngoài ra, thuộc tính CommandText của đối tượng Command trong thuộc tính
UpdateCommand của DataAdapter được gán như sau:
ordersUpdateCommand.CommandText =
"UPDATE Orders " +
"SET " +
" CustomerID = @NewCustomerID " +
"WHERE OrderID = @OldOrderID";

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.

9.9.3.3. Trường hợp thứ 3


Giả sử, tùy chọn Cascade Update Related Fields không được chọn và thuộc tính
UpdateRule được gán là None. Ngoài ra, thuộc tính CommandText của đối tượng
Command trong thuộc tính UpdateCommand của DataAdapter được gán như trong
trường hợp 2.
Đoạn mã sau gán giá trị cho thuộc tính UpdateRule của ChildKeyConstraint là
None.
myDataSet.Relations["CustomersOrders"].
ChildKeyConstraint.UpdateRule = Rule.None;

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.

9.9.3.4. Kết luận


Các ví dụ trên mô tả các tình huống rắc rối và gây căng thẳng khi buộc phải dùng
các ràng buộc để quản lý các thay đổi dữ liệu trên cột khóa chính. Vậy thì phải làm thế
nào khi bạn muốn thực hiện việc thay đổi giá trị trên khóa chính mà không phải quan
tâm đến những lội như trên?

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.

9.10. Kết chương

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.

Bài tập chương 9

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.

Những nội dung chính sẽ được trình bày


 Lớp SqlTransaction
 Các thuộc tính giao dịch cơ sở dữ liệu ACID
 Thiết lập điểm lưu (savepoint)
 Gán mức độ độc lập cho giao dịch cơ sở dữ liệu
 Tìm hiểu về các chế độ khóa của SQL Server

10.1. Lớp SqlTransaction


Có 3 lớp Transaction: SqlTransaction, OleDbTransaction và OdbcTransaction. Đối
tượng Transaction được dùng để biểu diễn một giao dịch cơ sở dữ liệu. SqlTransaction
biểu diễn một giao dịch trong cơ sở dữ liệu SQL Server. Các thuộc tính và phương thức
của lớp SqlTransaction được liệt kê trong các bảng sau:

Trang 343
Giáo trình lập trình cơ sở dữ liệu

PROPERTY TYPE DESCRIPTION


Connection SqlConnection Lấy kết nối cho giao dịch cơ sở dữ liệu.
IsolationLevel IsolationLevel Lấy mức độ độc lập của giao dịch.
Bảng 10.1. Một số thuộc tính của SqlTransaction

METHOD RET TYPE DESCRIPTION


Commit() void Thực hiện việc xác nhận các lệnh SQL trong giao dịch.
Rollback() void Phương thức có nhiều dạng. Thực hiện việc hủy bỏ các
lệnh SQL trong giao dịch.
Save() void Tạo một điểm lưu (savepoint) trong giao dịch để có thể
hủy bỏ một phần của giao dịch. Phương thức này nhận
một tham số kiểu chuỗi làm tên của savepoint. Sau đó,
bạn có thể quay ngược (roll back) giao dịch về
savepoint.
Bảng 10.2. Một số phương thức của SqlTransaction

10.2. Thiết lập điểm lưu (savepoint)


Ta có thể tạo một điểm lưu ở bất cứ vị trí nào trong giao dịch. Việc này cho phép
hủy bỏ các thay đổi tạo ra trên dữ liệu sau một điểm lưu nào đó bằng cách quay ngược
trở lại bởi lệnh Rollback. Điều này rất hữu ích khi bạn có một giao dịch dài vì nếu có
một lỗi sau điểm lưu mà bạn đã thiết lập, bạn không cần phải hủy bỏ toàn bộ giao dịch
và làm lại từ đầu.

10.2.1. Thiết lập điểm lưu dùng T-SQL


Trong T-SQL, lệnh SAVE TRANSACTION (hoặc viết tắt SAVE TRANS ) được
dùng để tạo một điểm lưu. Cú pháp của lệnh này như sau:
SAVE TRANS[ACTION] { savepointName | @savepointVariable }

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

-- step 1: begin the transaction

BEGIN TRANSACTION

-- step 2: insert a row into the Customers table

INSERT INTO Customers ( CustomerID, CompanyName )


VALUES ( 'J8COM', 'J8 Company' )

-- step 3: set a savepoint

SAVE TRANSACTION SaveCustomer

-- step 4: insert a row into the Orders table

INSERT INTO Orders ( CustomerID ) VALUES ( 'J8COM' );

-- step 5: rollback to the savepoint set in step 3

ROLLBACK TRANSACTION SaveCustomer

-- step 6: commit the transaction

COMMIT TRANSACTION

-- step 7: select the new row from the Customers table

SELECT CustomerID, CompanyName


FROM Customers
WHERE CustomerID = 'J8COM'

-- step 8: attempt to select the row from the Orders table


-- that was rolled back in step 5

SELECT OrderID, CustomerID


FROM Orders
WHERE CustomerID = 'J8COM'

-- step 9: delete the new row from the Customers table

DELETE FROM Customers


WHERE CustomerID = 'J8COM'

Trang 345
Giáo trình lập trình cơ sở dữ liệu

10.2.2. Tạo điểm lưu bằng đối tượng SqlTransaction


Ta có thể tạo một điểm lưu trong đối tượng SqlTransaction bằng cách gọi phương
thức Save() kèm theo tham số chứa tên mà bạn muốn gán cho điểm lưu.
Ví dụ:
Giả sử, bạn đã có một đối tượng SqlTransaction tên là mySqlTransaction. Đoạn mã
sau tạo một điểm lưu có tên SaveCustomer bằng cách gọi phương thức Save.
mySqlTransaction.Save("SaveCustomer");

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

// step 1: create a SqlTransaction object

SqlTransaction mySqlTransaction =
mySqlConnection.BeginTransaction();

// step 2: create a SqlCommand and set its Transaction property


// to mySqlTransaction

SqlCommand mySqlCommand = mySqlConnection.CreateCommand();


mySqlCommand.Transaction = mySqlTransaction;

// step 3: insert a row into the Customers table


Console.WriteLine("Inserting a row into the Customers table " +
"with a CustomerID of J8COM");

mySqlCommand.CommandText =
"INSERT INTO Customers ( CustomerID, CompanyName ) " +
"VALUES ( 'J8COM', 'J8 Company' )";

int numberOfRows = mySqlCommand.ExecuteNonQuery();


Console.WriteLine("Number of rows inserted = " + numberOfRows);

// step 4: set a savepoint by calling the Save() method of


// mySqlTransaction, passing the name "SaveCustomer" to
// the Save() method

mySqlTransaction.Save("SaveCustomer");

// step 5: insert a row into the Orders table


Console.WriteLine("Inserting a row into the Orders table " +
"with a CustomerID of J8COM");

mySqlCommand.CommandText =
"INSERT INTO Orders ( CustomerID ) " +
"VALUES ( 'J8COM' )";

numberOfRows = mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows inserted = " + numberOfRows);

// step 6: rollback to the savepoint set in step 4

Console.WriteLine("Performing a rollback to the savepoint");

mySqlTransaction.Rollback("SaveCustomer");

// step 7: display the new row added to the Customers table

mySqlCommand.CommandText =
"SELECT CustomerID, CompanyName " +
"FROM Customers " +
"WHERE CustomerID = 'J8COM'";

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();


while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"CustomerID\"] = " +
mySqlDataReader["CustomerID"]);
Console.WriteLine("mySqlDataReader[\"CompanyName\"] = " +

Trang 347
Giáo trình lập trình cơ sở dữ liệu

mySqlDataReader["CompanyName"]);
}
mySqlDataReader.Close();

// step 8: delete the new row from the Customers table


Console.WriteLine("Deleting row with CustomerID of J8COM");

mySqlCommand.CommandText =
"DELETE FROM Customers " +
"WHERE CustomerID = 'J8COM'";

numberOfRows = mySqlCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows deleted = " + numberOfRows);

// step 9: commit the transaction


Console.WriteLine("Committing the transaction");
mySqlTransaction.Commit();

mySqlConnection.Close();
}
}

Kết quả chạy chương trình


Inserting a row into the Customers table with a CustomerID of J8COM
Number of rows inserted = 1

Inserting a row into the Orders table with a CustomerID of J8COM


Number of rows inserted = 1

Performing a rollback to the savepoint


mySqlDataReader["CustomerID"] = J8COM
mySqlDataReader["CompanyName"] = J8 Company

Deleting row with CustomerID of J8COM


Number of rows deleted = 1

Committing the transaction

10.3. Gán mức độ độc lập của giao dịch


Mức độ độc lập của giao dịch là mức độ tách biệt của các thay đổi được tạo ra bởi
một giao dịch này với các giao dịch đồng thời được thực hiện bởi những người dùng
hay ứng dụng khác.
Trước khi đi vào chi tiết về mức độ độc lập giao dịch, ta cần phải hiểu các nhóm vấn
đề có thể xảy ra khi các giao dịch đồng thời cố gắng truy xuất đến cùng một số dòng
trong bảng. Có 3 nhóm vấn đề tiềm tàng có thể xảy ra khi xử lý các giao dịch:
 Phantoms: giao dịch 1 đọc các dòng trả về bởi lệnh SQL có mệnh đề WHERE.
Sau đó, giao dịch 2 chèn thêm một dòng mới, nhưng dòng này cũng có thể thỏa
mệnh đề WHERE trong truy vấn trước, được thực hiện bởi giao dịch 1. Tiếp
theo, giao dịch 1 đọc lại các dòng bởi truy vấn như trước nhưng bây giờ, kết quả
được bổ sung bởi lệnh INSERT của giao dịch 2. Dòng mới này được gọi là

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

ISOLATION LEVEL DESCRIPTION


RepeatableRead Cho phép Phantoms nhưng cấm nonrepeatable and dirty reads
Serializable Phantoms, nonrepeatable reads, and dirty reads đều bị
cấm.
Unspecified Một mức độ độc lập khác mà người dùng chỉ định nhưng
không thể xác định. SQL Server không hỗ trợ mức độc lập
này.
Bảng 10.4. Enum System.Data.IsolationLevel

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:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

Đ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

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

BEGIN TRANSACTION

SELECT CustomerID, CompanyName

Trang 350
Giáo trình lập trình cơ sở dữ liệu

FROM Customers
WHERE CustomerID IN ('ALFKI', 'J8COM')

INSERT INTO Customers ( CustomerID, CompanyName )


VALUES ( 'J8COM', 'J8 Company' )

UPDATE Customers
SET CompanyName = 'Widgets Inc.'
WHERE CustomerID = 'ALFKI'

SELECT CustomerID, CompanyName


FROM Customers
WHERE CustomerID IN ('ALFKI', 'J8COM')

COMMIT TRANSACTION

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

BEGIN TRANSACTION

UPDATE Customers
SET CompanyName = 'Alfreds Futterkiste'
WHERE CustomerID = 'ALFKI'

DELETE FROM Customers


WHERE CustomerID = 'J8COM'

SELECT CustomerID, CompanyName


FROM Customers
WHERE CustomerID IN ('ALFKI', 'J8COM')

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' )";

int numberOfRows = serializableCommand.ExecuteNonQuery();

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')";

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();


while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"CustomerID\"] = " +
mySqlDataReader["CustomerID"]);
Console.WriteLine("mySqlDataReader[\"CompanyName\"] = " +
mySqlDataReader["CompanyName"]);
}
mySqlDataReader.Close();
}

public static void PerformSerializableTransaction(


SqlConnection mySqlConnection )
{
Console.WriteLine("\nIn PerformSerializableTransaction()");

// create a SqlTransaction object and start the transaction


// by calling the BeginTransaction() method of the SqlConnection
// object, passing the IsolationLevel of Serializable to the
// method

SqlTransaction serializableTrans =
mySqlConnection.BeginTransaction(IsolationLevel.Serializable);

// create a SqlCommand and set its Transaction property


// to serializableTrans

SqlCommand serializableCommand =
mySqlConnection.CreateCommand();
serializableCommand.Transaction = serializableTrans;

// call the DisplayRows() method to display rows from


// the Customers table

DisplayRows(serializableCommand);

// insert a new row into the Customers table


Console.WriteLine("Inserting new row into Customers table " +
"with CustomerID of J8COM");

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

")";

int numberOfRows = serializableCommand.ExecuteNonQuery();


Console.WriteLine("Number of rows inserted = " + numberOfRows);

// update a row in the Customers table


Console.WriteLine("Setting CompanyName to 'Widgets Inc.' for " +
"row with CustomerID of ALFKI");

serializableCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Widgets Inc.' " +
"WHERE CustomerID = 'ALFKI'";

numberOfRows = serializableCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows updated = " + numberOfRows);

DisplayRows(serializableCommand);

// commit the transaction


serializableTrans.Commit();
}

public static void PerformReadCommittedTransaction(


SqlConnection mySqlConnection )
{
Console.WriteLine("\nIn PerformReadCommittedTransaction()");

// create a SqlTransaction object and start the transaction


// by calling the BeginTransaction() method of the SqlConnection
// object, passing the IsolationLevel of ReadCommitted to the
// method (ReadCommitted is actually the default)

SqlTransaction readCommittedTrans =
mySqlConnection.BeginTransaction(IsolationLevel.ReadCommitted);

// create a SqlCommand and set its Transaction property


// to readCommittedTrans

SqlCommand readCommittedCommand =
mySqlConnection.CreateCommand();
readCommittedCommand.Transaction = readCommittedTrans;

// update a row in the Customers table


Console.WriteLine("Setting CompanyName to 'Alfreds " +
" Futterkiste' for row with CustomerID of ALFKI");

readCommittedCommand.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Alfreds Futterkiste' " +
"WHERE CustomerID = 'ALFKI'";

int numberOfRows = readCommittedCommand.ExecuteNonQuery();


Console.WriteLine("Number of rows updated = " + numberOfRows);

// delete the new row from the Customers table


Console.WriteLine("Deleting row with CustomerID of J8COM");

readCommittedCommand.CommandText =

Trang 354
Giáo trình lập trình cơ sở dữ liệu

"DELETE FROM Customers " +


"WHERE CustomerID = 'J8COM'";

numberOfRows = readCommittedCommand.ExecuteNonQuery();
Console.WriteLine("Number of rows deleted = " + numberOfRows);

DisplayRows(readCommittedCommand);

// commit the transaction

readCommittedTrans.Commit();
}

public static void Main()


{
SqlConnection mySqlConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa"
);
mySqlConnection.Open();

PerformSerializableTransaction(mySqlConnection);
PerformReadCommittedTransaction(mySqlConnection);

mySqlConnection.Close();
}
}

Kết quả chạy chương trình

In PerformSerializableTransaction()
mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Alfreds Futterkiste

Inserting new row into Customers table with CustomerID of J8COM


Number of rows inserted = 1

Setting CompanyName to 'Widgets Inc.' for row with CustomerID of ALFKI


Number of rows updated = 1

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

Deleting row with CustomerID of J8COM


Number of rows deleted = 1

mySqlDataReader["CustomerID"] = ALFKI
mySqlDataReader["CompanyName"] = Alfreds Futterkiste

Trang 355
Giáo trình lập trình cơ sở dữ liệu

10.4. Tìm hiểu về các chế độ khóa của SQL Server


SQL Server sử dụng các khóa (locks) để thực thi sự độc lập giao dịch và bảo đảm
tính toàn vẹn của thông tin được lưu trong cơ sở dữ liệu. Các khóa ngăn cản một người
dùng đọc dữ liệu hay thay đổi một dòng nào đó trong khi dòng đó đang được cập nhật
bởi một người dùng khác. Chẳng hạn, khi bạn cập nhật một dòng, dòng đó sẽ bị khóa để
ngăn không cho người dùng khác cập nhật nó tại cùng một thời điểm.

10.4.1. Các loại khóa trong SQL Server


SQL Server sử dụng nhiều loại khóa, Bảng 10.5 liệt kê một vài loại thông dụng. Các
khóa được liệt kê tăng dần theo “độ mịn” của khóa (locking granularity). Mức độ mịn
của khóa ám chỉ kích thước của tài nguyên đang bị khóa. Chẳng hạn, row lock tốt hơn
page lock.

LOCK TYPE DESCRIPTION


Row (RID) Khóa được gán trên một dòng của bảng, đại diện cho bộ dịnh danh
của dòng, được dùng để xác dịnh duy nhất một dòng.
Key (KEY) Được đặt trên một dòng cùng với chỉ mục, được dùng để bảo vệ
vùng khóa trong các giao dịch SERIALIZABLE.
Page (PAG) Được đặt trên một trang (page). Một trang có thể chứa 8KB các
dòng hoặc dữ liệu chỉ mục.
Extent (EXT) Được đặt trên một phạm vi rộng (extent) gồm 8 trang dữ liệu hay
chỉ mục liên tiếp nhau.
Table (TAB) Được đặt trên một bảng và khóa tất cả các dòng, các chỉ mục có
trong bảng.
Database (DB) Sử dụng để khóa toàn bộ cơ sở dữ liệu khi người quản trị cơ sở
dữ liệu đưa nó vào chế độ chỉ một người dùng (trường hợp cần
trì hệ thống).
Bảng 10.5. Các loại khóa trong SQL Server

10.4.2. Các chế độ khóa


SQL Server sử dụng nhiều chế độ khóa khác nhau, xác định bởi mức độ của khóa
được đặt trên tài nguyên. Các chế độ khóa được liệt kê trong Bảng 10.6.

LOCKING MODE DESCRIPTION


Shared (S) Chỉ ra rằng một giao dịch chuẩn bị đọc dữ liệu từ một tài
nguyên dùng một lệnh SELECT. Ngăn chặn các giao dịch khác
thay đổi trên các tài nguyên bị khóa. Khóa Shared được giải
phóng ngay khi dữ liệu đã đọc xong, trừ khi mức độc lập giao
dịch được gán là REPEATABLE READ hay SERIALIZABLE.
Update (U) Chỉ ra rằng một giao dịch muốn cập nhật một tài nguyên dùng
lệnh INSERT, UPDATE hay DELETE. Khóa phải được tăng cường
(escalated) thành một khóa dành riêng (độc quyền-exclusive)
trước khi giao dịch thực sự xảy ra.
Exclusive (X) Cho phép các giao dịch cập nhận tài nguyên dùng lệnh INSERT,
UPDATE hay DELETE. Các giao dịch khác không thể đọc hoặc ghi
đè lên tài nguyên đang có khóa dành riêng (Exclusive).

Trang 356
Giáo trình lập trình cơ sở dữ liệu

LOCKING MODE DESCRIPTION


Intent shared Chỉ ra rằng giao dịch muốn đặt khóa Shared trên một vài tài
(IS) nguyên với mức độ mịn hơn trong tài nguyên đang dùng. Chẳng
hạn, đặt khóa IS lên một bảng chỉ ra rằng giao dịch muốn đặt
một khóa Shared trên một vài trang (pages) hoặc dòng (rows)
trong bảng đó. Các giao dịch khác không thể đặt khóa dành
riêng (Exclusive) lên tài nguyên đã có khóa IS.
Intent exclusive Chỉ ra rằng giao dịch muốn đặt một khóa dành riêng lên một
(IX) tài nguyên với mức độ mịn hơn. Các giao dịch khác không thể
đặt khóa dành riêng lên tài nguyên đã có khóa IX.
Shared with Chỉ ra rằng giao dịch muốn đọc tất cả các tài nguyên có mức
intent exclusive độ mịn hơn và cập nhật một vài tài nguyên đó. Chẳng hạn, đặt
(SIX) một khóa SIX lên một bảng có nghĩa là giao dịch muốn đọc tất
cả các dòng trong bảng và cập nhật một vài dòng trong đó.
Các giao dịch khác không thể đặt khóa dành riêng trên một
tài nguyên đã có khóa SIX.
Schema Chỉ ra rằng một lệnh DDL - Data Definition Language – sắp
modification được thực hiện trên một lược đồ cấu trúc tài nguyên (chẳng
(Sch-M) hạn, DROP TABLE). Các giao dịch khác không thể đặt một khóa
lên tài nguyên đã có khóa Sch-M.
Schema stability Chỉ ra rằng một lệnh SQL sử dụng tài nguyên được thực hiện.
(Sch-S) Ví dụ như lệnh SELECT. Các giao dịch khác có thể đặt một
khóa lên tài nguyên đã có khóa Sch-S. Chỉ có khóa Sch-M bị
cấm.
Bulk update (BU) Chỉ ra rằng các thao tác sao chép đồng loạt để tải các dòng
vào một bảng sắp được thực hiện. Một khóa BU cho phép các
tiến trình khác sao chép một lượng lớn dữ liệu đồng thời vào
cùng một bảng nhưng ngăn chặn các tiến trình khác khỏi việc
sao chép dữ liệu từ việc truy xuất đến bảng.
Bảng 10.6. Các chế độ khóa của SQL Server

10.4.3. Xem thông tin các khóa của SQL Server


Với SQL Server, ta có thể xem thông tin “khóa” của một cơ sở dữ liệu bằng cách
dùng chương trình Enterprise Manager. Mở thư mục Management  mở nút Current
Activity  chọn Locks/Process ID hoặc Locks/Object.

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ó).

10.5. Chặn giao dịch – Transaction Blocking


Một giao dịch có thể chặn hoặc cấm không cho một giao dịch khác lấy các tài
nguyên mà nó đang chiếm giữ bởi khóa. Chẳng hạn, ta bắt đầu một giao dịch bằng cách
dùng lệnh T-SQL như sau:
USE Northwind
BEGIN TRANSACTION
UPDATE Customers
SET CompanyName = 'Widgets Inc.'
WHERE CustomerID = 'ALFKI'

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.

Hình 10.4. Chặn giao dịch


Để xác nhận giao dịch trước và giải phóng khóa, ta gọi lệnh T-SQL: COMMIT
TRANSACTION. Việc này cho phép lệnh UPDATE thứ 2 lấy các khóa thích hợp (hay
chiếm quyền) để cập các dòng và tiếp tục (Hình 10.5).

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

10.6. Đặt thời gian TimeOut cho khóa


Theo mặc định, một lệnh SQL sẽ chờ vô hạn, cho đến khi nó lấy được khóa. Ta có
thể thay đổi điều này bằng cách thực thi lệnh SET LOCK_TIMEOUT.
Ví dụ: lệnh sau đặt thời gian hết hạnh cho một lệnh là 1 giây (1000 mili giây).
SET LOCK_TIMEOUT 1000

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();

10.7. Chặn và đọc các giao dịch Serializable / Repeatable


Các giao dịch Serializable và Repeatable khóa các dòng mà chúng đang nhận để các
giao dịch khác không thể cập nhật được. Các giao dịch Serializable và Repeatable thực
hiện điều này để bảo đảm rằng các dòng sẽ không bị thay đổi sau khi chúng được đọc.
Ví dụ:
Nếu bạn lấy một dòng từ bảng Customers với CustomerID là ALFKI bằng cách
dùng một giao dịch Serializable và sau đó cố gắng cập nhật dòng này bởi một giao dịch

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.

Chương trình 10.5: Chặn giao dịch


/*
Block.cs illustrates how a serializable command locks
the rows it retrieves so that a second transaction
cannot get a lock to update one of these retrieved rows
that has already been locked
*/

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')";

SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();


while (mySqlDataReader.Read())
{
Console.WriteLine("mySqlDataReader[\"CustomerID\"] = " +
mySqlDataReader["CustomerID"]);
Console.WriteLine("mySqlDataReader[\"CompanyName\"] = " +
mySqlDataReader["CompanyName"]);
}
mySqlDataReader.Close();
}

public static void Main()


{
// create and open two SqlConnection objects
SqlConnection serConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );

SqlConnection rcConnection =
new SqlConnection(
"server=localhost;database=Northwind;uid=sa;pwd=sa" );

serConnection.Open();
rcConnection.Open();

// create the first SqlTransaction object and start the


// transaction by calling the BeginTransaction() method of the

Trang 361
Giáo trình lập trình cơ sở dữ liệu

// SqlConnection object, passing the IsolationLevel of


// Serializable to the method

SqlTransaction serializableTrans =
serConnection.BeginTransaction(IsolationLevel.Serializable);

// create a SqlCommand and set its Transaction property


// to serializableTrans

SqlCommand serializableCommand =
serConnection.CreateCommand();
serializableCommand.Transaction = serializableTrans;

// call the DisplayRows() method to display rows from


// the Customers table;
// this causes the rows to be locked, if you comment
// out the following line then the INSERT and UPDATE
// performed later by the second transaction will succeed

DisplayRows(serializableCommand); // ******************

// create the second SqlTransaction object

SqlTransaction readCommittedTrans =
rcConnection.BeginTransaction(IsolationLevel.ReadCommitted);

// create a SqlCommand and set its Transaction property


// to readCommittedTrans

SqlCommand readCommittedCommand =
rcConnection.CreateCommand();
readCommittedCommand.Transaction = readCommittedTrans;

// set the lock timeout to 1 second using the


// SET LOCK_TIMEOUT command

readCommittedCommand.CommandText = "SET LOCK_TIMEOUT 1000";


readCommittedCommand.ExecuteNonQuery();

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' " +
")";

int numberOfRows = readCommittedCommand.ExecuteNonQuery();


Console.WriteLine("Number of rows inserted = " +
numberOfRows);

// update the ALFKI row in the Customers table


Console.WriteLine("Setting CompanyName to " +
'Widgets Inc.' for for row with CustomerID of ALFKI");

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);

// display the new rows and rollback the changes


DisplayRows(readCommittedCommand);

Console.WriteLine("Rolling back changes");


readCommittedTrans.Rollback();
}

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:

Giao dịch 1 (T1):


BEGIN TRANSACTION
UPDATE Customers
SET CompanyName = 'Widgets Inc.'
WHERE CustomerID = 'ALFKI'
UPDATE Products
SET ProductName = 'Widget'
WHERE ProductID = 1
COMMIT TRANSACTION

Giao dịch 2 (T2):


BEGIN TRANSACTION
UPDATE Products
SET ProductName = ' Chai'
WHERE ProductID = 1
UPDATE Customers
SET CompanyName = ' Alfreds Futterkiste'
WHERE CustomerID = 'ALFKI'
COMMIT TRANSACTION

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.

SET DEADLOCK_PRIORITY { LOW | NORMAL | @variable }


Trong đó:
 LOW: cho biết giao dịch có độ ưu tiên thấp và là giao dịch sẽ bị loại bỏ khi xảy
ra deadlock.
 NORMAL: cho biết quy tắc mặc định được áp dụng. Nghĩa là giao dịch có chi
phí quay lại thấp nhất sẽ bị loại bỏ.
 @variable: là một biến T-SQL kiểu kí tự chứa giá trị LOW hoặc NORMAL.

Ví dụ: Thiết lập độ ưu tiên deadlock là LOW.


SET DEADLOCK_PRIORITY LOW
Gán độ ưu tiên deadlock bằng C#:
t2Command.CommandText = "SET DEADLOCK_PRIORITY LOW";
t2Command.ExecuteNonQuery();

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.

Chương trình 10.6: Deadlock


/*
Deadlock.cs illustrates how two transactions can
deadlock each other
*/

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" );

public static SqlConnection t2Connection =


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

// declare two SqlTransaction objects


public static SqlTransaction t1Trans;
public static SqlTransaction t2Trans;

// declare two SqlCommand objects


public static SqlCommand t1Command;
public static SqlCommand t2Command;

public static void UpdateCustomerT1()


{
// update the row with a CustomerID of ALFKI
// in the Customers table using t1Command

Console.WriteLine("Setting CompanyName to 'Widgets Inc.' " +


"for row with CustomerID of ALFKI using t1Command");

t1Command.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Widgets Inc.' " +
"WHERE CustomerID = 'ALFKI'";

int numberOfRows = t1Command.ExecuteNonQuery();


Console.WriteLine("Number of rows updated = " + numberOfRows);
}

public static void UpdateProductT2()


{
// update the row with a ProductID of 1
// in the Products table using t2Command

Console.WriteLine("Setting ProductName to 'Widget' " +


"for the row with ProductID of 1 using t2Command");

t2Command.CommandText =
"UPDATE Products " +
"SET ProductName = 'Widget' " +
"WHERE ProductID = 1";

int numberOfRows = t2Command.ExecuteNonQuery();


Console.WriteLine("Number of rows updated = " + numberOfRows);
}

public static void UpdateProductT1()


{
// update the row with a ProductID of 1
// in the Products table using t1Command

Console.WriteLine("Setting ProductName to 'Chai' " +


"for the row with ProductID of 1 using t1Command");

t1Command.CommandText =
"UPDATE Products " +
"SET ProductName = 'Chai' " +
"WHERE ProductID = 1";

int numberOfRows = t1Command.ExecuteNonQuery();


Console.WriteLine("Number of rows updated = " + numberOfRows);
}

Trang 366
Giáo trình lập trình cơ sở dữ liệu

public static void UpdateCustomerT2()


{
// update the row with a CustomerID of ALFKI
// in the Customers table using t2Command

Console.WriteLine("Setting CompanyName to 'Alfreds Futterkiste' " +


"for row with CustomerID of ALFKI using t2Command");

t2Command.CommandText =
"UPDATE Customers " +
"SET CompanyName = 'Alfreds Futterkiste' " +
"WHERE CustomerID = 'ALFKI'";

int numberOfRows = t2Command.ExecuteNonQuery();


Console.WriteLine("Number of rows updated = " + numberOfRows);
}

public static void Main()


{
// open the first connection, begin the first transaction,
// and set the lock timeout to 5 seconds

t1Connection.Open();
t1Trans = t1Connection.BeginTransaction();
t1Command = t1Connection.CreateCommand();
t1Command.Transaction = t1Trans;
t1Command.CommandText = "SET LOCK_TIMEOUT 5000";
t1Command.ExecuteNonQuery();

// open the second connection, begin the second transaction,


// and set the lock timeout to 5 seconds

t2Connection.Open();
t2Trans = t2Connection.BeginTransaction();
t2Command = t2Connection.CreateCommand();
t2Command.Transaction = t2Trans;
t2Command.CommandText = "SET LOCK_TIMEOUT 5000";
t2Command.ExecuteNonQuery();

// set DEADLOCK_PRIORITY to LOW for the second transaction


// so that it is the transaction that is rolled back

t2Command.CommandText = "SET DEADLOCK_PRIORITY LOW";


t2Command.ExecuteNonQuery();

// create four threads that will perform the interleaved updates

Thread updateCustThreadT1 = new Thread(new


ThreadStart(UpdateCustomerT1));

Thread updateProdThreadT2 = new Thread(new


ThreadStart(UpdateProductT2));

Thread updateProdThreadT1 = new Thread(new


ThreadStart(UpdateProductT1));

Thread updateCustThreadT2 = new Thread(new


ThreadStart(UpdateCustomerT2));

Trang 367
Giáo trình lập trình cơ sở dữ liệu

// start the threads to actually perform the interleaved updates


updateCustThreadT1.Start();
updateProdThreadT2.Start();
updateProdThreadT1.Start();
updateCustThreadT2.Start();
}
}

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

Setting CompanyName to 'Widgets Inc.' for row


with CustomerID of ALFKI using t1Command
Number of rows updated = 1

Setting ProductName to 'Widget' for the row


with ProductID of 1 using t2Command
Number of rows updated = 1

Setting ProductName to 'Chai' for the row


with ProductID of 1 using t1Command

Setting CompanyName to 'Alfreds Futterkiste' for row


with CustomerID of ALFKI using t2Command

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.

Rerun the transaction.


at System.Data.SqlClient.SqlConnection.OnError(SqlException
exception,
TdsParserState state)

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()

Number of rows updated = 1

10.9. Kết chương

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.

Bài tập chương 10

Trang 369
Giáo trình lập trình cơ sở dữ liệu

TÀI LIỆU THAM KHẢO

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

You might also like