database programming in csharp

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

Upload: vanmuoiit

Post on 18-Nov-2014

197 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Database Programming in CSharp

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

Page 2: Database Programming in CSharp

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,

Page 3: Database Programming in CSharp

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

Page 4: Database Programming in CSharp

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

Trang 4

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

Page 5: Database Programming in CSharp

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

Trang 5

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

Page 6: Database Programming in CSharp

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

Trang 6

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

Page 7: Database Programming in CSharp

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

Trang 7

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

Page 8: Database Programming in CSharp

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

Trang 8

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

Page 9: Database Programming in CSharp

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

Trang 9

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

Page 10: Database Programming in CSharp

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

Trang 10

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

Page 11: Database Programming in CSharp

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

Trang 11

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

Page 12: Database Programming in CSharp

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

Trang 12

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.

Page 13: Database Programming in CSharp

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

Trang 13

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

Page 14: Database Programming in CSharp

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

Trang 14

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ó

Page 15: Database Programming in CSharp

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

Trang 15

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

Page 16: Database Programming in CSharp

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

Trang 16

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

Page 17: Database Programming in CSharp

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

Trang 17

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

Page 18: Database Programming in CSharp

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

Trang 18

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

Page 19: Database Programming in CSharp

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

Trang 19

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:

Page 20: Database Programming in CSharp

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

Trang 20

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.

Page 21: Database Programming in CSharp

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

Trang 21

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

Page 22: Database Programming in CSharp

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

Trang 22

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

Page 23: Database Programming in CSharp

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

Trang 23

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

Page 24: Database Programming in CSharp

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

Trang 24

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

Page 25: Database Programming in CSharp

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

Trang 25

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)

Page 26: Database Programming in CSharp

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

Trang 26

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 ]

Page 27: Database Programming in CSharp

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

Trang 27

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.

Page 28: Database Programming in CSharp

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

Trang 28

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.

Page 29: Database Programming in CSharp

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

Trang 29

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

Page 30: Database Programming in CSharp

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

Trang 30

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.

Page 31: Database Programming in CSharp

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

Trang 31

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

Page 32: Database Programming in CSharp

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

Trang 32

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:

Page 33: Database Programming in CSharp

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

Trang 33

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.

Page 34: Database Programming in CSharp

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

Trang 34

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

Page 35: Database Programming in CSharp

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

Trang 35

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

Page 36: Database Programming in CSharp

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

Trang 36

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.

Page 37: Database Programming in CSharp

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

Trang 37

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 (

Page 38: Database Programming in CSharp

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

Trang 38

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.

Page 39: Database Programming in CSharp

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

Trang 39

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

Page 40: Database Programming in CSharp

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

Trang 40

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.

Page 41: Database Programming in CSharp

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

Trang 41

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

Page 42: Database Programming in CSharp

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

Trang 42

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.

Page 43: Database Programming in CSharp

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

Trang 43

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

Page 44: Database Programming in CSharp

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

Trang 44

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.

Page 45: Database Programming in CSharp

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

Trang 45

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 )

Page 46: Database Programming in CSharp

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

Trang 46

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

Page 47: Database Programming in CSharp

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

Trang 47

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

Page 48: Database Programming in CSharp

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

Trang 48

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

Page 49: Database Programming in CSharp

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

Trang 49

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.

Page 50: Database Programming in CSharp

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

Trang 50

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.

Page 51: Database Programming in CSharp

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

Trang 51

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

Page 52: Database Programming in CSharp

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

Trang 52

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

Page 53: Database Programming in CSharp

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

Trang 53

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

Page 54: Database Programming in CSharp

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

Trang 54

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.

Page 55: Database Programming in CSharp

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

Trang 55

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

Page 56: Database Programming in CSharp

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

Trang 56

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

Page 57: Database Programming in CSharp

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

Trang 57

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

Page 58: Database Programming in CSharp

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

Trang 58

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

Page 59: Database Programming in CSharp

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

Trang 59

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.

Page 60: Database Programming in CSharp

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

Trang 60

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:

Page 61: Database Programming in CSharp

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

Trang 61

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

Page 62: Database Programming in CSharp

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

Trang 62

- 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

Page 63: Database Programming in CSharp

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

Trang 63

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

Page 64: Database Programming in CSharp

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

Trang 64

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.

Page 65: Database Programming in CSharp

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

Trang 65

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ó

Page 66: Database Programming in CSharp

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

Trang 66

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

Page 67: Database Programming in CSharp

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

Trang 67

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

Page 68: Database Programming in CSharp

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

Trang 68

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.

Page 69: Database Programming in CSharp

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

Trang 69

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

Page 70: Database Programming in CSharp

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

Trang 70

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:

Page 71: Database Programming in CSharp

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

Trang 71

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

Page 72: Database Programming in CSharp

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

Trang 72

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

Page 73: Database Programming in CSharp

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

Trang 73

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

Page 74: Database Programming in CSharp

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

Trang 74

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

Page 75: Database Programming in CSharp

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

Trang 75

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.

Page 76: Database Programming in CSharp

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

Trang 76

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

Page 77: Database Programming in CSharp

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

Trang 77

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.

Page 78: Database Programming in CSharp

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

Trang 78

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

Page 79: Database Programming in CSharp

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

Trang 79

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.

Page 80: Database Programming in CSharp

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

Trang 80

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.

Page 81: Database Programming in CSharp

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

Trang 81

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

Page 82: Database Programming in CSharp

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

Trang 82

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

Page 83: Database Programming in CSharp

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

Trang 83

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

Page 84: Database Programming in CSharp

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

Trang 84

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

Page 85: Database Programming in CSharp

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

Trang 85

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

Page 86: Database Programming in CSharp

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

Trang 86

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

Page 87: Database Programming in CSharp

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

Trang 87

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.

Page 88: Database Programming in CSharp

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

Trang 88

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.

Page 89: Database Programming in CSharp

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

Trang 89

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)

Page 90: Database Programming in CSharp

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

Trang 90

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.

Page 91: Database Programming in CSharp

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

Trang 91

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

Page 92: Database Programming in CSharp

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

Trang 92

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

Page 93: Database Programming in CSharp

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

Trang 93

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à

Page 94: Database Programming in CSharp

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

Trang 94

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.

Page 95: Database Programming in CSharp

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

Trang 95

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.

Page 96: Database Programming in CSharp

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

Trang 96

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à

Page 97: Database Programming in CSharp

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

Trang 97

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.

Page 98: Database Programming in CSharp

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

Trang 98

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

Page 99: Database Programming in CSharp

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

Trang 99

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

Page 100: Database Programming in CSharp

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

Trang 100

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:

Page 101: Database Programming in CSharp

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

Trang 101

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 = " +

Page 102: Database Programming in CSharp

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

Trang 102

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ổ

Page 103: Database Programming in CSharp

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

Trang 103

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

Page 104: Database Programming in CSharp

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

Trang 104

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

Page 105: Database Programming in CSharp

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

Trang 105

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.

Page 106: Database Programming in CSharp

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

Trang 106

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

Page 107: Database Programming in CSharp

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

Trang 107

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

Page 108: Database Programming in CSharp

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

Trang 108

); }

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

Page 109: Database Programming in CSharp

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

Trang 109

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

Page 110: Database Programming in CSharp

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

Trang 110

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

Page 111: Database Programming in CSharp

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

Trang 111

Hình 3.4. Tạo kết nối đến SQL Server

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

Page 112: Database Programming in CSharp

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

Trang 112

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 thức này.

Hình 3.6. Các sự kiện của

SqlConnection

Page 113: Database Programming in CSharp

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

Trang 113

- 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

Page 114: Database Programming in CSharp

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

Trang 114

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.

Page 115: Database Programming in CSharp

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

Trang 115

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.

Page 116: Database Programming in CSharp

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

Trang 116

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à

Page 117: Database Programming in CSharp

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

Trang 117

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

Page 118: Database Programming in CSharp

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

Trang 118

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.

Page 119: Database Programming in CSharp

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

Trang 119

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.

Page 120: Database Programming in CSharp

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

Trang 120

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\"] = " +

Page 121: Database Programming in CSharp

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

Trang 121

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

Page 122: Database Programming in CSharp

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

Trang 122

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

Page 123: Database Programming in CSharp

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

Trang 123

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;

Page 124: Database Programming in CSharp

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

Trang 124

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

Page 125: Database Programming in CSharp

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

Trang 125

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

Page 126: Database Programming in CSharp

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

Trang 126

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

Page 127: Database Programming in CSharp

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

Trang 127

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

Page 128: Database Programming in CSharp

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

Trang 128

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 =

Page 129: Database Programming in CSharp

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

Trang 129

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 =

Page 130: Database Programming in CSharp

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

Trang 130

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;

Page 131: Database Programming in CSharp

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

Trang 131

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

Page 132: Database Programming in CSharp

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

Trang 132

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;

Page 133: Database Programming in CSharp

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

Trang 133

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.

Page 134: Database Programming in CSharp

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

Trang 134

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 BASE64

Cho biết dữ liệu nhị phân được trả về sau truy vấn sẽ được mã 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 /*

Page 135: Database Programming in CSharp

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

Trang 135

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

Page 136: Database Programming in CSharp

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

Trang 136

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

Page 137: Database Programming in CSharp

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

Trang 137

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 =

Page 138: Database Programming in CSharp

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

Trang 138

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

Page 139: Database Programming in CSharp

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

Trang 139

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

Page 140: Database Programming in CSharp

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

Trang 140

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

Page 141: Database Programming in CSharp

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

Trang 141

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

Page 142: Database Programming in CSharp

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

Trang 142

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.

Page 143: Database Programming in CSharp

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

Trang 143

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 (" +

Page 144: Database Programming in CSharp

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

Trang 144

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

Page 145: Database Programming in CSharp

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

Trang 145

- 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

Page 146: Database Programming in CSharp

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

Trang 146

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

Page 147: Database Programming in CSharp

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

Trang 147

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

Page 148: Database Programming in CSharp

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

Trang 148

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

Page 149: Database Programming in CSharp

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

Trang 149

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

Page 150: Database Programming in CSharp

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

Trang 150

- 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

Page 151: Database Programming in CSharp

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

Trang 151

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

Page 152: Database Programming in CSharp

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

Trang 152

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

Page 153: Database Programming in CSharp

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

Trang 153

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

Page 154: Database Programming in CSharp

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

Trang 154

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

Page 155: Database Programming in CSharp

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

Trang 155

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;

Page 156: Database Programming in CSharp

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

Trang 156

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.

Page 157: Database Programming in CSharp

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

Trang 157

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

Page 158: Database Programming in CSharp

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

Trang 158

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

Page 159: Database Programming in CSharp

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

Trang 159

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

Page 160: Database Programming in CSharp

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

Trang 160

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

Page 161: Database Programming in CSharp

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

Trang 161

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.

Page 162: Database Programming in CSharp

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

Trang 162

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

Page 163: Database Programming in CSharp

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

Trang 163

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.

Page 164: Database Programming in CSharp

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

Trang 164

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

Page 165: Database Programming in CSharp

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

Trang 165

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

Page 166: Database Programming in CSharp

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

Trang 166

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

Page 167: Database Programming in CSharp

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

Trang 167

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ố

Page 168: Database Programming in CSharp

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

Trang 168

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

Page 169: Database Programming in CSharp

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

Trang 169

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

Page 170: Database Programming in CSharp

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

Trang 170

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

Page 171: Database Programming in CSharp

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

Trang 171

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.

Page 172: Database Programming in CSharp

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

Trang 172

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

Page 173: Database Programming in CSharp

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

Trang 173

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 =

Page 174: Database Programming in CSharp

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

Trang 174

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

Page 175: Database Programming in CSharp

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

Trang 175

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

Page 176: Database Programming in CSharp

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

Trang 176

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

Page 177: Database Programming in CSharp

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

Trang 177

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

Page 178: Database Programming in CSharp

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

Trang 178

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

Page 179: Database Programming in CSharp

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

Trang 179

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

Page 180: Database Programming in CSharp

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

Trang 180

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

Page 181: Database Programming in CSharp

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

Trang 181

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

Page 182: Database Programming in CSharp

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

Trang 182

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

Page 183: Database Programming in CSharp

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

Trang 183

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

Page 184: Database Programming in CSharp

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

Trang 184

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

Page 185: Database Programming in CSharp

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

Trang 185

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

Page 186: Database Programming in CSharp

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

Trang 186

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:

Page 187: Database Programming in CSharp

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

Trang 187

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

Page 188: Database Programming in CSharp

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

Trang 188

"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

Page 189: Database Programming in CSharp

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

Trang 189

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

Page 190: Database Programming in CSharp

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

Trang 190

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

Page 191: Database Programming in CSharp

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

Trang 191

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

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

Page 192: Database Programming in CSharp

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

Trang 192

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.

Page 193: Database Programming in CSharp

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

Trang 193

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.

Page 194: Database Programming in CSharp

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

Trang 194

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

Thuộc tính đọc - ghi. Lấy hay gán dạng thao tác xử lý khi có một cột hay bảng 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-

hemaAction Thuộc tính đọc - ghi. Lấy hay gán dạng 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

Page 195: Database Programming in CSharp

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

Trang 195

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-MappingCo-llection

Thuộc tính chỉ đọc. Lấy về một đối tượng DataTableMappingCollection chứa ánh xạ 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 DataTable[]

Phương thức có nhiều dạng. Thêm một hoặc 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,

Page 196: Database Programming in CSharp

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

Trang 196

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.

Page 197: Database Programming in CSharp

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

Trang 197

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.

Page 198: Database Programming in CSharp

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

Trang 198

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

Thuộc tính chỉ đọc. Lấy giá trị của khung 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-Collection

Thuộc tính chỉ đọc. Lấy tập (Property-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-Collection

Thuộc tính chỉ đọc. Lấy tập các quan hệ (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-Collection

Thuộc tính chỉ đọc. Lấy danh sách các bảng (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

TYPE DESCRIPTION

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

Page 199: Database Programming in CSharp

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

Trang 199

METHOD RETURN TYPE

DESCRIPTION

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.

Page 200: Database Programming in CSharp

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

Trang 200

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

Page 201: Database Programming in CSharp

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

Trang 201

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.

Page 202: Database Programming in CSharp

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

Trang 202

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

Page 203: Database Programming in CSharp

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

Trang 203

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

Page 204: Database Programming in CSharp

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

Trang 204

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

Page 205: Database Programming in CSharp

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

Trang 205

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

Page 206: Database Programming in CSharp

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

Trang 206

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

Page 207: Database Programming in CSharp

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

Trang 207

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

Page 208: Database Programming in CSharp

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

Trang 208

đượ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";

Page 209: Database Programming in CSharp

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

Trang 209

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.

Page 210: Database Programming in CSharp

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

Trang 210

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

Page 211: Database Programming in CSharp

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

Trang 211

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

Page 212: Database Programming in CSharp

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

Trang 212

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

Page 213: Database Programming in CSharp

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

Trang 213

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)

Page 214: Database Programming in CSharp

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

Trang 214

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

Page 215: Database Programming in CSharp

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

Trang 215

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)

Page 216: Database Programming in CSharp

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

Trang 216

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

Page 217: Database Programming in CSharp

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

Trang 217

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

Page 218: Database Programming in CSharp

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

Trang 218

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

Page 219: Database Programming in CSharp

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

Trang 219

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.

Page 220: Database Programming in CSharp

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

Trang 220

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 =

Page 221: Database Programming in CSharp

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

Trang 221

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 =

Page 222: Database Programming in CSharp

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

Trang 222

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

Page 223: Database Programming in CSharp

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

Trang 223

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

Page 224: Database Programming in CSharp

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

Trang 224

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

Page 225: Database Programming in CSharp

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

Trang 225

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;

Page 226: Database Programming in CSharp

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

Trang 226

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

Page 227: Database Programming in CSharp

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

Trang 227

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.

Page 228: Database Programming in CSharp

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

Trang 228

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

Page 229: Database Programming in CSharp

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

Trang 229

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

Page 230: Database Programming in CSharp

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

Trang 230

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

Page 231: Database Programming in CSharp

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

Trang 231

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

Page 232: Database Programming in CSharp

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

Trang 232

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

Page 233: Database Programming in CSharp

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

Trang 233

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:

Page 234: Database Programming in CSharp

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

Trang 234

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

Page 235: Database Programming in CSharp

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

Trang 235

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.

Page 236: Database Programming in CSharp

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

Trang 236

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

Page 237: Database Programming in CSharp

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

Trang 237

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

TYPE DESCRIPTION

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.

Page 238: Database Programming in CSharp

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

Trang 238

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

TYPE DESCRIPTION

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

Page 239: Database Programming in CSharp

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

Trang 239

METHOD RETURN TYPE

DESCRIPTION

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à

Page 240: Database Programming in CSharp

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

Trang 240

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.

Page 241: Database Programming in CSharp

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

Trang 241

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

Page 242: Database Programming in CSharp

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

Trang 242

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

Page 243: Database Programming in CSharp

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

Trang 243

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

Page 244: Database Programming in CSharp

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

Trang 244

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

Page 245: Database Programming in CSharp

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

Trang 245

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.

Page 246: Database Programming in CSharp

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

Trang 246

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

Page 247: Database Programming in CSharp

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

Trang 247

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

Page 248: Database Programming in CSharp

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

Trang 248

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

Page 249: Database Programming in CSharp

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

Trang 249

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

Page 250: Database Programming in CSharp

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

Trang 250

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

Page 251: Database Programming in CSharp

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

Trang 251

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

Page 252: Database Programming in CSharp

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

Trang 252

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

Page 253: Database Programming in CSharp

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

Trang 253

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 = " +

Page 254: Database Programming in CSharp

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

Trang 254

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

Page 255: Database Programming in CSharp

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

Trang 255

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

Page 256: Database Programming in CSharp

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

Trang 256

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

Page 257: Database Programming in CSharp

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

Trang 257

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

Page 258: Database Programming in CSharp

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

Trang 258

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

Page 259: Database Programming in CSharp

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

Trang 259

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

Page 260: Database Programming in CSharp

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

Trang 260

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

Page 261: Database Programming in CSharp

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

Trang 261

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.

Page 262: Database Programming in CSharp

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

Trang 262

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

Page 263: Database Programming in CSharp

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

Trang 263

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

Page 264: Database Programming in CSharp

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

Trang 264

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

Page 265: Database Programming in CSharp

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

Trang 265

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

Page 266: Database Programming in CSharp

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

Trang 266

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

Page 267: Database Programming in CSharp

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

Trang 267

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.

Page 268: Database Programming in CSharp

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

Trang 268

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

Page 269: Database Programming in CSharp

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

Trang 269

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 =

Page 270: Database Programming in CSharp

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

Trang 270

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:

Page 271: Database Programming in CSharp

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

Trang 271

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

Page 272: Database Programming in CSharp

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

Trang 272

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

Page 273: Database Programming in CSharp

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

Trang 273

@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

Page 274: Database Programming in CSharp

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

Trang 274

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.

Page 275: Database Programming in CSharp

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

Trang 275

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

Page 276: Database Programming in CSharp

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

Trang 276

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;

Page 277: Database Programming in CSharp

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

Trang 277

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

Page 278: Database Programming in CSharp

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

Trang 278

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

Page 279: Database Programming in CSharp

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

Trang 279

} 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 =

Page 280: Database Programming in CSharp

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

Trang 280

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 )

Page 281: Database Programming in CSharp

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

Trang 281

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

Page 282: Database Programming in CSharp

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

Trang 282

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

Page 283: Database Programming in CSharp

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

Trang 283

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.

Page 284: Database Programming in CSharp

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

Trang 284

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 )

Page 285: Database Programming in CSharp

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

Trang 285

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

Page 286: Database Programming in CSharp

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

Trang 286

{ 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

Page 287: Database Programming in CSharp

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

Trang 287

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

Page 288: Database Programming in CSharp

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

Trang 288

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

Page 289: Database Programming in CSharp

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

Trang 289

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.

Page 290: Database Programming in CSharp

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

Trang 290

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

Page 291: Database Programming in CSharp

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

Trang 291

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

Page 292: Database Programming in CSharp

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

Trang 292

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.

Page 293: Database Programming in CSharp

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

Trang 293

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

Page 294: Database Programming in CSharp

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

Trang 294

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

TYPE DESCRIPTION

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

Page 295: Database Programming in CSharp

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

Trang 295

METHOD RETURN TYPE

DESCRIPTION

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)

Page 296: Database Programming in CSharp

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

Trang 296

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

Page 297: Database Programming in CSharp

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

Trang 297

} 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

Page 298: Database Programming in CSharp

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

Trang 298

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.

Page 299: Database Programming in CSharp

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

Trang 299

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.

Page 300: Database Programming in CSharp

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

Trang 300

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

TYPE DESCRIPTION

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.

Page 301: Database Programming in CSharp

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

Trang 301

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;

Page 302: Database Programming in CSharp

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

Trang 302

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

Page 303: Database Programming in CSharp

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

Trang 303

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

Page 304: Database Programming in CSharp

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

Trang 304

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

Page 305: Database Programming in CSharp

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

Trang 305

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\"] = " +

Page 306: Database Programming in CSharp

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

Trang 306

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

Page 307: Database Programming in CSharp

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

Trang 307

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

Page 308: Database Programming in CSharp

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

Trang 308

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(

Page 309: Database Programming in CSharp

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

Trang 309

"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

Page 310: Database Programming in CSharp

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

Trang 310

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.

Page 311: Database Programming in CSharp

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

Trang 311

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(

Page 312: Database Programming in CSharp

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

Trang 312

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

Page 313: Database Programming in CSharp

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

Trang 313

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

Page 314: Database Programming in CSharp

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

Trang 314

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.

Page 315: Database Programming in CSharp

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

Trang 315

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

Page 316: Database Programming in CSharp

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

Trang 316

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.

Page 317: Database Programming in CSharp

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

Trang 317

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

Page 318: Database Programming in CSharp

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

Trang 318

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

Page 319: Database Programming in CSharp

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

Trang 319

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

Page 320: Database Programming in CSharp

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

Trang 320

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.

Page 321: Database Programming in CSharp

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

Trang 321

Đ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

Page 322: Database Programming in CSharp

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

Trang 322

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.

Page 323: Database Programming in CSharp

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

Trang 323

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)

Page 324: Database Programming in CSharp

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

Trang 324

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

Page 325: Database Programming in CSharp

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

Trang 325

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

Page 326: Database Programming in CSharp

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

Trang 326

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

Page 327: Database Programming in CSharp

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

Trang 327

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

Page 328: Database Programming in CSharp

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

Trang 328

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

Page 329: Database Programming in CSharp

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

Trang 329

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

Page 330: Database Programming in CSharp

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

Trang 330

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

Page 331: Database Programming in CSharp

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

Trang 331

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

Page 332: Database Programming in CSharp

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

Trang 332

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

Page 333: Database Programming in CSharp

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

Trang 333

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.

Page 334: Database Programming in CSharp

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

Trang 334

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á

Page 335: Database Programming in CSharp

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

Trang 335

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

Page 336: Database Programming in CSharp

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

Trang 336

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.

Page 337: Database Programming in CSharp

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

Trang 337

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.

Page 338: Database Programming in CSharp

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

Trang 338

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

Page 339: Database Programming in CSharp

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

Trang 339

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.

Page 340: Database Programming in CSharp

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

Trang 340

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.

Page 341: Database Programming in CSharp

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

Trang 341

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?

Page 342: Database Programming in CSharp

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

Trang 342

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

Page 343: Database Programming in CSharp

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

Trang 343

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:

Page 344: Database Programming in CSharp

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

Trang 344

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.

Page 345: Database Programming in CSharp

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

Trang 345

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

Page 346: Database Programming in CSharp

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

Trang 346

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

Page 347: Database Programming in CSharp

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

Trang 347

// 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\"] = " +

Page 348: Database Programming in CSharp

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

Trang 348

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à

Page 349: Database Programming in CSharp

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

Trang 349

“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 LEVEL

DESCRIPTION

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.

Page 350: Database Programming in CSharp

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

Trang 350

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

Page 351: Database Programming in CSharp

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

Trang 351

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

Page 352: Database Programming in CSharp

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

Trang 352

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

Page 353: Database Programming in CSharp

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

Trang 353

*/ 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' " +

Page 354: Database Programming in CSharp

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

Trang 354

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

Page 355: Database Programming in CSharp

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

Trang 355

"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

Page 356: Database Programming in CSharp

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

Trang 356

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

Page 357: Database Programming in CSharp

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

Trang 357

LOCKING MODE DESCRIPTION Intent shared (IS)

Chỉ ra rằng giao dịch muốn đặt khóa Shared trên một vài tài 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 (IX)

Chỉ ra rằng giao dịch muốn đặt một khóa dành riêng lên một 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 intent exclusive (SIX)

Chỉ ra rằng giao dịch muốn đọc tất cả các tài nguyên có mức độ mịn hơn và cập nhật một vài tài nguyên đó. Chẳng hạn, đặt 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 modification (Sch-M)

Chỉ ra rằng một lệnh DDL - Data Definition Language – sắp được thực hiện trên một lược đồ cấu trúc tài nguyên (chẳng 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 (Sch-S)

Chỉ ra rằng một lệnh SQL sử dụng tài nguyên được thực hiện. 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.

Page 358: Database Programming in CSharp

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

Trang 358

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

Page 359: Database Programming in CSharp

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

Trang 359

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

Page 360: Database Programming in CSharp

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

Trang 360

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

Page 361: Database Programming in CSharp

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

Trang 361

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

Page 362: Database Programming in CSharp

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

Trang 362

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

Page 363: Database Programming in CSharp

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

Trang 363

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

Page 364: Database Programming in CSharp

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

Trang 364

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.

Page 365: Database Programming in CSharp

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

Trang 365

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

Page 366: Database Programming in CSharp

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

Trang 366

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

Page 367: Database Programming in CSharp

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

Trang 367

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

Page 368: Database Programming in CSharp

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

Trang 368

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

Page 369: Database Programming in CSharp

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

Trang 369

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

Page 370: Database Programming in CSharp

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

Trang 370

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