MỤC LỤC
Phần 1
Cơ bản về ngôn ngữ lập trình C#...........................................................................................................................4
    Bài thực hành 1.1. Chương trình đầu tiên........................................................................................................... 4
       Tóm tắt.............................................................................................................................................................4
       Kỹ thuật được trình bày...................................................................................................................................4
       Trình tự thực hiện............................................................................................................................................4
    Bài thực hành 1.2. Module hóa chương trình......................................................................................................7
       Tóm tắt.............................................................................................................................................................7
       Kỹ thuật được trình bày...................................................................................................................................7
       Trình tự thực hiện............................................................................................................................................7
    Bài thực hành 1.3. Tạo thư viện sử dụng chung.................................................................................................. 9
      Tóm tắt.............................................................................................................................................................9
      Kỹ thuật được trình bày...................................................................................................................................9
      Trình tự thực hiện............................................................................................................................................9
      Mở rộng.........................................................................................................................................................10
    Bài thực hành 1.4. Tam giác Pascal ..................................................................................................................11
       Tóm tắt...........................................................................................................................................................11
       Kỹ thuật được trình bày.................................................................................................................................11
       Trình tự thực hiện..........................................................................................................................................11
       Mở rộng.........................................................................................................................................................12
    Bài thực hành 1.5. Tam giác Pascal – array version.........................................................................................13
       Tóm tắt...........................................................................................................................................................13
       Kỹ thuật được trình bày.................................................................................................................................13
       Trình tự thực hiện..........................................................................................................................................13
       Mở rộng.........................................................................................................................................................13
    Bài thực hành 1.6. MyTYPE...............................................................................................................................14
       Tóm tắt...........................................................................................................................................................14
       Kỹ thuật được trình bày.................................................................................................................................14
       Trình tự thực hiện..........................................................................................................................................14
       Mở rộng.........................................................................................................................................................14
    Bài thực hành 1.7. Quản lý sinh viên.................................................................................................................15
       Tóm tắt...........................................................................................................................................................15
       Kỹ thuật được trình bày.................................................................................................................................15
       Trình tự thực hiện..........................................................................................................................................15
       Yêu cầu thêm.................................................................................................................................................20

Phần 2
Lập trình ứng dụng với winforms........................................................................................................................21
    Bài thực hành 2.1 helloWinForms.....................................................................................................................21
      Tóm tắt...........................................................................................................................................................21
      Kỹ thuật được trình bày.................................................................................................................................21
      Trình tự thực hiện..........................................................................................................................................21
      Mở rộng.........................................................................................................................................................32
    Bài thực hành 2.2 usingControls....................................................................................................................... 33
      Tóm tắt...........................................................................................................................................................33
Kỹ thuật được trình bày.................................................................................................................................33
        Trình tự thực hiện..........................................................................................................................................33
    Bài thực hành 2.3 textFormat............................................................................................................................ 37
      Tóm tắt...........................................................................................................................................................37
      Kỹ thuật được trình bày.................................................................................................................................37
      Trình tự thực hiện..........................................................................................................................................37
      Mở rộng.........................................................................................................................................................44
    Bài thực hành 2.4 myCalculator........................................................................................................................45
       Tóm tắt...........................................................................................................................................................45
       Kỹ thuật được trình bày:...............................................................................................................................45
       Trình tự thực hiện..........................................................................................................................................45
       Mở rộng.........................................................................................................................................................49
    Bài thực hành 2.5 myNotePAD...........................................................................................................................50
       Tóm tắt...........................................................................................................................................................50
       Kỹ thuật được trình bày.................................................................................................................................50
       Trình tự thực hiện..........................................................................................................................................50
       Mở rộng.........................................................................................................................................................54
    Bài thực hành 2.6 Quản lý sinh viên - WinForms version.................................................................................55
       Tóm tắt...........................................................................................................................................................55
       Kỹ thuật được trình bày.................................................................................................................................56
       Trình tự thực hiện..........................................................................................................................................56
       Mở rộng.........................................................................................................................................................61
    Bài thực hành 2.7 myFileViewer........................................................................................................................62
       Tóm tắt...........................................................................................................................................................62
       Kỹ thuật được trình bày.................................................................................................................................62
       Trình tự thực hiện..........................................................................................................................................62
       Mở rộng.........................................................................................................................................................72

Phần 3
Xử lý dữ liệu với ADO.NET.................................................................................................................................73
    Kiến thức cơ bản về ADO.NET 2.0....................................................................................................................73
       3.1 Kiến trúc tổng quan của ADO.NET........................................................................................................73
       3.2 Tổng quan về các mô hình xử lý dữ liệu trong ADO.NET: Mô hình Kết nối (Connected Model) và Mô
       hình Ngắt Kết nối (Disconnected Model)......................................................................................................75
       3.3 Làm việc với mô hình Kết nối trong ADO.NET.....................................................................................78
       3.3.4 Ví dụ.....................................................................................................................................................89
       3.5 Làm việc với mô hình Ngắt kết nối: DataSet và DataTable....................................................................96
       3.5 Sử dụng Data Binding...........................................................................................................................113
       3.6 Lựa chọn giữa mô hình Kết nối và mô hình Ngắt kết nối.....................................................................113
       3.6 Tạo đối tượng DataSet...........................................................................................................................115
       3.7 Kết hợp giữa nhiều bảng.......................................................................................................................118
       3.8 Thay đổi các bản ghi của cơ sở dữ liệu.................................................................................................123
       3.9 Truy cập và hiển thị dữ liệu...................................................................................................................124
       3.10 Cập nhật một dòng dữ liệu...................................................................................................................124
       3.11 Xóa một dòng dữ liệu..........................................................................................................................126
       3.12 Tạo một dòng dữ liệu mới................................................................................................................... 127

Phần 4
Xây dựng ứng dụng Web với WebForms..........................................................................................................139
    4.1 Tìm hiểu về Web Forms..............................................................................................................................139




                                    Bài tập thực hành Chuyên đề Visual Studio .NET                                                 2
4.2 Các sự kiện của Web Forms.......................................................................................................................139
       4.2.1 Sự kiện PostBack và Non-PostBack...................................................................................................140
       4.2.2 Trạng thái của ứng dụng Web (State).................................................................................................140
       4.2.3 Chu trình sống của một Web-Form....................................................................................................140
    4.3 Điều khiển xác nhận hợp............................................................................................................................143
    4.4 Một số ví dụ mẫu minh họa........................................................................................................................143
    4.5 Các dịch vụ Web.........................................................................................................................................158

Phần 5
Phụ lục..................................................................................................................................................................163
    Phụ lục A Chuỗi kết nối cho các loại nguồn dữ liệu........................................................................................163
    Phụ lục B Bảng tương quan/chuyển đổi kiểu dữ liệu ở .NET Framework với các Data Provider..................163

Phần
Tài liệu tham khảo...............................................................................................................................................165




                                     Bài tập thực hành Chuyên đề Visual Studio .NET                                                   3
PHẦN 1
                          CƠ BẢN VỀ NGÔN NGỮ LẬP TRÌNH C#
                                                         NH


Bài thực hành 1.1. Chương trình đầu tiên

Tóm tắt
Bài thực hành này giúp bạn làm quen với môi trường Visual Studio 2005 và các thao
tác nhập xuất cơ bản thông qua giao diện bàn phím. Cụ thể, chương trình yêu cầu
người sử dụng nhập hai số, sau đó in ra màn hình tổng, tích và thương của hai số này.

Kỹ thuật được trình bày
  - Làm quen với môi trường Visual Studio 2005. Cấu trúc một solution, project và
      các tài nguyên có liên quan
   -   Cách thức sử dụng thư viện MSDN để tra cứu, hướng dẫn
   -   Sử dụng thao tác nhập xuất cơ bản

Trình tự thực hiện
   1. Khởi động Microsoft Visual Studio 2005. Nhấn Ctrl + Shift + N hoặc chọn
      menu tương ứng là File  New  Project để tạo mới một project




   2. Chọn loại ứng dụng cần phát triển là Visual C#  Console Application. Chọn
       thư mục chứa project và đặt tên cho project. Về mặt thực chất, Visual Studio
       coi project thuộc về một solution nào đó, và một solution có thể chứa nhiều



                     Bài tập thực hành Chuyên đề Visual Studio .NET         4
project. Tuy nhiên, trong nhiều “bài toán” đơn giản (như ví dụ của chúng ta
   chẳng hạn), một solution chỉ có 1 project.
3. Đặt tên cho project của chúng ta thành firstApp. Sau khi nhấn nút OK, hãy khảo
   sát xem cấu trúc của thư mục chứa solution của chúng ta. Bạn phải luôn nắm
   chắc về ý nghĩa của các tập tin, thư mục được tạo ra trong quá trình làm việc.
4. Gõ mã lệnh như minh họa vào trong phần mã nguồn của tập tin Program.cs




5. Sử dụng MSDN để tra cứu các thông tin bạn chưa biết về:
      a. Console và các phương thức ReadLine(), WriteLine() của nó
      b. Cách chuyển đổi kiểu chuỗi thành số, ví dụ như int.Parse()
6. Nhấn Ctrl + F5 để thực hiện chạy chương trình. Sau đó quan sát cấu trúc thư
   mục của solution, cho biết sự thay đổi của nó so với khi mới được tạo ra ở bước
   3.
7. Thử thay đổi kết câu lệnh


   float thuong = (float)x / y;
   thành
   float thuong = x / y;
   rồi chạy chương trình, quan sát kết quả và rút ra kết luận.




               Bài tập thực hành Chuyên đề Visual Studio .NET    5
8. Sử dụng thêm các cấu trúc lệnh khác để tinh chỉnh hoạt động của chương trình
   (xử lý phép chia cho 0, …)




              Bài tập thực hành Chuyên đề Visual Studio .NET   6
Bài thực hành 1.2. Module hóa chương trình

Tóm tắt
Viết chương trình nhập vào một số nguyên N từ bàn phím. Sau đó

   a. In ra màn hình giá trị N!.
   b. Nhập thêm một số nguyên K từ bàn phím. Sau đó in ra CKN = N!/(K!*(N-K)!)

Kỹ thuật được trình bày
  - Cấu trúc, cách quản lý logic và vật lý, cách làm việc của solution và project
   -   Thực hiện chia nhỏ ứng dụng thành để chuyên môn hóa các phần
   -   Cơ bản về các kiểu phương thức trong một lớp

Trình tự thực hiện
   1. Mở solution đã làm ở Bài thực hành 1.1. Chỉnh sửa tên của solution từ
      “firstApp” thành “day1” cho có ý nghĩa. Xem cấu trúc thư mục của solution sau
      khi thay đổi.




   2. Thêm một project vào solution này bằng menu lệnh File  Add  New
       project… . Tương tự như cách tạo mới project ở bài thực hành trước, chọn thể
       loại project là Console Application. Đặt tên cho project mới là “modular”.
   3. Quan sát cấu trúc cây thư mục của solution trong cửa sổ Solution Explorer và
      cả trong Windows Explorer. Để ý rằng, trong cửa sổ Solution Explorer, project
      firstApp được tô đậm. Điều này có nghĩa, firstApp đóng vai trò là “Startup



                    Bài tập thực hành Chuyên đề Visual Studio .NET         7
project”. Khi nhấn Ctrl + F5 thì project này sẽ được gọi thực thi chứ không phải
   là project modular mà ta mới tạo ra.
       Trong cửa sổ Solution Explorer, nhắp phải chuột lên “modular”. Trong
   menu hiện ra, chọn menu lệnh “Set as Startup project” để thiết lập lại startup
   project cho solution.
4. Việc nhập n, tính n! rồi in kết quả bạn hoàn toàn có thể thực hiện được bằng các
   câu lệnh đơn giản. Tuy nhiên, để tăng tính rõ ràng và tái sử dụng, bạn nên tạo ra
   một phương thức để hỗ trợ việc tính toán n!. Xem mã lệnh bên dưới




5. Chạy thử chương trình để xem kết quả. Hãy để ý rằng, khai báo phương thức
   giaiThua là static long giaiThua(int n). Thử xóa static trong khai báo này rồi
   chạy lại chương trình.
    Lỗi nhận được cho biết chỉ các phương thức static mới được triệu gọi, sử
       dụng lẫn nhau
6. Bằng cách tạo ra phương thức long giaiThua() như trên, chúng ta có thể giải
   quyết được vấn đề tính Ckn một cách dễ dàng. Lời gọi để tính Ckn như sau:
   GiaiThua(n)/(GiaiThua(n-k)*GiaiThua(k))
7. Hãy tạo ra một phương thức để tính tổ hợp chập k của n phần tử (bạn tự quyết
   định các tham số và kiểu dữ liệu trả về).




                 Bài tập thực hành Chuyên đề Visual Studio .NET        8
Bài thực hành 1.3. Tạo thư viện sử dụng chung

Tóm tắt
Trong thực tế, một ứng dụng có thể là có khả năng thực thi (executable) hoặc chỉ đơn
thuần là thư viện để chứa các chức năng, lớp đối tượng.

   Bài thực hành này hướng dẫn bạn tạo thư viện chứa các phương thức thường dùng.
Với mục đích minh họa, thư viện này chỉ chứa 2 hàm tiện ích giúp tính giai thừa và tổ
hợp chập. Sau khi biên dịch, bạn sẽ có được một file nhị với phần mở rộng là DLL.
Thư viện này, khi cần, sẽ được tham chiếu đến trong các ứng dụng khác.

Kỹ thuật được trình bày
  - Tạo loại ứng dụng loại thư viện

Trình tự thực hiện
   1. Tạo mới một project, đặt tên là commonUtils (common utilities - các tiện ích
      dùng chung). Chú ý chọn loại ứng dụng cần tạo là Class Library
   2. Mặc định Visual Studio 2005 sẽ tạo ra trong namespace CommonUtils một lớp
      tên là Class1. Đổi tên lớp này lại thành Math. Sau đó cài đặt các phương thức
      như sau:




   3. Rõ ràng, đây không phải là một chương trình để chạy như các ứng dụng bạn đã
      viết trước đó - class Math không có phương thức static public Main() – tức là
      bạn không thể nhấn Ctrl + F5 để chạy chương trình. Biên dịch project này bằng
      menu lệnh Build  Build commonUtils. Kết quả, bạn sẽ có một thư viện
      commonUtils.dll trong thư mục binRelease hoặc binDebug của project tùy


                  Bài tập thực hành Chuyên đề Visual Studio .NET   9
theo cách chọn chế độ biên dịch. Thư viện này sẽ được dùng để tham chiếu đến
      trong các ứng dụng cần nó.

Mở rộng
Bổ sung các phương thức thường dùng khác vào thư viện, chẳng hạn như phương thức
xác định xem một số có phải là nguyên tố hay không, phương thức hoán đổi giá trị của
hai số cho trước, …




                 Bài tập thực hành Chuyên đề Visual Studio .NET   10
Bài thực hành 1.4. Tam giác Pascal

Tóm tắt
Viết chương trình nhập một số nguyên N từ bàn phím, sau đó in ra màn hình N dòng
đầu tiên của tam giác Pascal.

Kỹ thuật được trình bày
  - Sử dụng thư viện có sẵn

Trình tự thực hiện
   1. Tạo mới một ứng dụng kiểu Console Application. Đặt tên project là
      pascalTriangle1
   2. Thực hiện bổ sung tham khảo đến thư viện commonUtils bằng cách:
          -   Nhắp phải chuột vào project pascalTriangle1 trong cửa sổ Solution
              Explorer
          -   Trong menu hiện ra, chọn Add Reference…




              Trong tab Browse của hộp thoại Add Reference, tìm đến thư viện
              commonUtils.dll đã tạo ra trước đó.
               Dễ thấy rằng thư viện được tham khảo đến không chỉ có dạng DLL
              mà có thể có các dạng khác, bao gồm EXE, OCX, …



                   Bài tập thực hành Chuyên đề Visual Studio .NET     11
3. Hoàn thiện phần mã nguồn có sử dụng tham chiếu đến thư viện vừa bổ sung
      như hình dưới:




Mở rộng
Hãy tự rút ra những ghi chú cần thiết về việc:

   -   Khai báo phương thức C(int n, int k) trong commonUtils là
       public static long C(int n, int k)

       static, public ở đây có ý nghĩa gì, có thể thay thế hoặc bỏ đi?

   -   Tương tự cho phương thức giaiThua(int n);
   -   Tại sao trong quá trình sử dụng phương thức C() lại phải ghi đầy đủ là
       commonUtils.Math.C()? Chỉ cần ghi Math.C() có được không?




                   Bài tập thực hành Chuyên đề Visual Studio .NET    12
Bài thực hành 1.5. Tam giác Pascal – array version

Tóm tắt
Sử dụng array để xây dựng tam giác Pascal như Bài thực hành 1.4.

Kỹ thuật được trình bày
  - Sử dụng array

Trình tự thực hiện
   1. Tạo mới một project kiểu Console Application với tên là pascalTriangle2
   2. Sử dụng các tính chất C00 = Ckk = 1, Cnk = Cn-1k-1 + Cn-1k , ta sẽ xây dựng một
      jagged array từ nhỏ đến lớn. Chi tiết như phần mã nguồn phía dưới:




Mở rộng
Có thể dùng array nhiều chiều trong trường hợp này không? Nếu có thì có sự khác
nhau nào so với dùng jagged array?



                  Bài tập thực hành Chuyên đề Visual Studio .NET    13
Bài thực hành 1.6. MyTYPE

Tóm tắt
Viết chương trình in nội dung văn bản ra màn hình (như lệnh TYPE ở Ms DOS). Tên
file được truyền theo tham số dòng lệnh.

Kỹ thuật được trình bày
  - Sử dụng tham số dòng lệnh được truyền vào
   -   Sử dụng namespace System.IO để đọc nội dung tập tin

Trình tự thực hiện
   1. Tạo mới một projecet kiểu Console Application với tên myTYPE
   2. Khai báo sử dụng thêm namespace System.IO rồi hoàn thiện phần mã nguồn
      như minh họa dưới




Mở rộng
  - Cách kiểm tra sự tồn tại của tập tin trước khi đọc và hiển thị nó như trên đã an
     toàn chưa? Có trường hợp nào mà sự tồn tại của tập tin lại chưa đảm bảo cho
     việc đọc và hiển thị nó? Giải quyết bằng cách nào?
   -   Thêm phần kiểm tra số lượng tham số truyền vào dòng lệnh để chương trình có
       thể hoạt động chính xác hơn (sử dụng args.Length)
   -   Sử dụng MSDN để tìm hiểu thêm các lớp khác trong namespace System.IO




                 Bài tập thực hành Chuyên đề Visual Studio .NET   14
Bài thực hành 1.7. Quản lý sinh viên

Tóm tắt
Viết chương trình quản lý sinh viên của một trường. Sinh viên có thể học các chuyên
ngành Công nghệ Thông tin, Vật lý, Ngữ văn. Mỗi chuyên ngành tương ứng có các
môn học khác nhau.

   •   Sinh viên khoa Công nghệ Thông tin phải học 3 môn Pascal, C# và SQL.
   • Sinh viên khoa Vật lý phải học 4 môn: Cơ học, Điện học, Quang học, Vật lý hạt
     nhân.
   •   Sinh viên khoa Văn phải học 2 môn Văn học cổ điển và Văn học Hiện đại
Chương trình cho phép nhập danh sách sinh viên, sau đó in danh sách sinh viên cùng
với điểm trung bình của họ ra màn hình.

In ra danh sách những sinh viên có điểm trung bình cao trên 5.0 ra màn hình. Thông
tin hiển thị có dạng Họ tên, Chuyên ngành đào tạo, Điểm trung bình.

Kỹ thuật được trình bày
  - Truy xuất tập tin có định dạng cho trước
   -   Sử dụng một phương thức của lớp String
   -   Các kỹ thuật hướng đối tượng được sử dụng trong bài toán thực tế

Trình tự thực hiện
   1. Trước khi tiến hành cài đặt, ta khảo sát qua sơ đồ lớp được sử dụng. Với những
      mô tả khá rõ ràng trong yêu cầu bài toán, ta có được cái nhìn tổng quan về các
      lớp như sau:




                   Bài tập thực hành Chuyên đề Visual Studio .NET   15
Lưu ý rằng, phương thức dtb() được cài đặt là virtual để chúng ta có thể
   override một cách cụ thể, chi tiết hơn trong các lớp kế thừa từ class SinhVien.
   Phương thức ToString() được cài đặt override từ lớp object để sử dụng trong
   việc in “nội dung” của đối tượng.
2. Tạo mới một project kiểu Console Application với tên là studentManager
3. Tại cây phân cấp Solution Explorer nhắp phải chuột và chọn Add New
   Item… Trong hộp thoại hiện ra, chọn tạo mới class SinhVien.cs




              Bài tập thực hành Chuyên đề Visual Studio .NET   16
4. Cài đặt các thành phần cơ bản cho lớp SinhVien




             Bài tập thực hành Chuyên đề Visual Studio .NET   17
5. Bổ sung thêm các class SinhVienCNTT, SinhVienVan, SinhVienVL theo phân
   tích thiết kế lớp từ trước. Dưới đây là phần mô tả cài đặt cho lớp SinhVienVan.
   Hai lớp còn lại SinhVienCNTT, SinhVienVL được cài đặt một cách tương tự.




              Bài tập thực hành Chuyên đề Visual Studio .NET   18
6. Trong phần chương trình (tập tin Program.cs) chúng ta thực hiện yêu cầu bài
   toán như sau:




             Bài tập thực hành Chuyên đề Visual Studio .NET   19
Yêu cầu thêm
  - In ra 3 sinh viên có điểm trung bình cao nhất trường.
   -   Chỉnh sửa để người sử dụng có thể nhập danh sách mà không biết trước số
       lượng sinh viên (sử dụng vòng lặp while, do, …)
   -   Chỉnh sửa để có thể nhập dữ liệu các sinh viên từ file.




                  Bài tập thực hành Chuyên đề Visual Studio .NET   20
PHẦN 2
                           LẬP TRÌNH ỨNG DUNG VỚI WINFORMS
                                    NH NG     ̣ NG

Phần này kéo dài trong 2 buổi. Các bài thực hành này sẽ được giới thiệu như là nội
dung cơ bản nhất mà sinh viên cần nắm. Các kỹ thuật khác sinh viên tự tham khảo và
trao đổi với lớp.

Bài thực hành 2.1 helloWinForms

Tóm tắt
Chương trình

Kỹ thuật được trình bày
  - Cấu trúc của và cơ chế hoạt động của một project Windows Form Application.
   -   Cơ chế xử lý sự kiện của các Control trong một Windows Form
   -   Một số phương thức, thuộc tính, sự kiện quan trọng của các điều khiển trong
       một Windows Form.

Trình tự thực hiện
   1. Tạo mới một ứng dụng kiểu Windows Form Application với tên là
      01-helloWindowsForm như hình vẽ




                    Bài tập thực hành Chuyên đề Visual Studio .NET        21
2. Theo mặc định, một solution với một project được tạo ra. Project này có một
   lớp Form1.
   Khảo sát nội dung của project trong Windows Explorer, chúng ta sẽ thấy cấu
   trúc của thư mục và các tập tin tương tự như hình dưới:




   Có thể thấy, mỗi Form được tạo ra tương ứng với 3 tập tin có tiếp đàu ngữ là
   giống nhau, lấy ví dụ là Form1
      •   Form1.Designer.cs: chứa các mã lệnh do Form Designer tự sinh ra tương
          ứng với các thao tác do người sử dụng kéo thả các Control từ ToolBox
          vào bề mặt Form hay thực hiện các thiết lập đối với các Control.



              Bài tập thực hành Chuyên đề Visual Studio .NET   22
•   Form1.cs: chứa phần mã lệnh và khai báo thêm do người sử dụng cài
          đặt.
      •   Form1.resx: chứa các mô tả, khai báo về các tài nguyên được sử dụng
          trong Form.


3. Chúng ta cũng có thể quan sát cấu trúc của solution hay project bằng cách khảo
   sát cửa sổ Solution Explorer:




4. Từ cửa sổ Solution Explorer, đổi tên tập tin Form1.cs thành FormMain.cs. Để ý
   rằng, cả ba tập tin liên quan đến Form1 đều được thay đổi theo một cách đồng
   bộ.
5. Thiết kế giao diện cho FormMain như hình vẽ




              Bài tập thực hành Chuyên đề Visual Studio .NET   23
6. Bước tiếp theo, chúng ta sẽ thực hiện cài đặt phương thức xử lý sự kiện Click
   của nút bấm btnCurrentTime:
      a. Chọn điều khiển nút bấm btnCurrentTime trong cửa số thiết kế Form.
      b. Ở trang Event trong cửa sổ Properties Windows, nhắp đúp chuột vào sự
         kiện Click (xem hình vẽ dưới). Form Designer sẽ sinh ra phương thức xử
         lý sự kiện có tên mặc định là btnCurrentTime_Click(…). (Phương thức
         xử lý sự kiện được mặc định đặt tên là <tênĐiềuKhiển>_<TênSựKiện>)




         Soạn thảo phần mã lệnh cho phương thức này như sau:




              Bài tập thực hành Chuyên đề Visual Studio .NET   24
7. Thực hiện chạy chương trình, khi nhấn vào nút bấm btnCurrentTime, một hộp
   thông báo được hiển thị ra như hình vẽ




8. Thực ra chúng ta có thể tự đặt tên cho phương thức xử lý sự kiện. Chẳng hạn,
   để cài đặt phương thức xử lý sự kiện MouseEnter cho nút bấm btnCurrentTime,
   trong cửa sổ Properties ở trang Events, tìm đến mục MouseEnter và:
      a. Nhập vào tên phương thức xử lý sự kiện: btn_MouseEnter
      b. Nhấn Enter
      c. FormDesigner sẽ tạo ra phương thức với tên tương ứng




             Bài tập thực hành Chuyên đề Visual Studio .NET   25
d. Tiến hành cài đặt mã lệnh cho phương thức xử lý sự kiện trên như sau:
         private void btn_MouseEnter(object sender, EventArgs e)
         {
            btnCurrentTime.ForeColor = Color.Red;
         }

9.    Tương tự, chúng ta cài đặt tiếp phương thức xử lý sự kiện MouseLeave cho nút
     bấm btnCurrentTime như sau
         private void btn_MouseLeave(object sender, EventArgs e)
         {
            btnCurrentTime.ForeColor = SystemColors.ControlText;
         }

10. Chạy chương trình và quan sát kết quả: Điều khiển nút bấm btnCurrentTime sẽ
     có hiệu ứng mouse hover khá ấn tượng: khi rê con trỏ chuột vào nút bấm
     btnCurrentTime, màu chữ của nó sẽ đổi sang màu đỏ; màu chữ của nút bấm trở
     thành bình thường (màu ControlText) khi con trỏ chuột rê ra khỏi nút bấm.
11. Để tìm hiểu kỹ hơn bản chất của việc gắn kết phương thức xử lý sự kiện, chúng
    ta nhắp đúp chuột vào FormMain.Designer.cs trong cửa sổ Solution Explorer để
    xem phần nội dung được sinh ra bởi Form Designer:




               Bài tập thực hành Chuyên đề Visual Studio .NET   26
Chú ý những phần được tô sáng trong hình vẽ nói trên; từ đó suy ra được bản
chất của việc gắn kết phương thức xử lý sự kiện trong khi thiết kế.




          Bài tập thực hành Chuyên đề Visual Studio .NET   27
12. Đóng file nội dung FormMain.Designer.cs lại. Các bước tiếp theo sẽ minh họa
        cách thức dùng chung một phương thức xử lý sự kiện cho nhiều đối tượng khác
        nhau.
    13. Trong cửa sổ thiết kế của FormMain, thực hiện
             a. Chọn cả hai đối tượng btnClose và btnAbout
             b. Trong trang Events của cửa sổ Properties, gõ tên phương thức xử lý sự
                 kiện Click cho cả hai điều khiển nút bấm này là btnTask_Click rồi nhấn
                 Enter (xem hình vẽ)




    14. Thực hiện cài đặt mã lệnh cho phương thức này như sau:
      private void btnTask_Click(object sender, EventArgs e)
      {
         if (sender == btnClose)
            this.Close();
         else if (sender == btnAbout)1
            MessageBox.Show("Day la chuong trinh minh hoa", "Thong bao");
      }

        Trong phương thức trên, chúng ta sử dụng đối số sender để nhận biết điều khiển
        nào phát sinh sự kiện. Chúng ta cũng có thể thực hiện như thế này:
1
  Thực ra không nhất thiết phải có nhánh else if, chỉ cần else là đủ, bởi vì ở đây chúng ta chỉ áp dụng phương
thức này cho hai điều khiển btnClose và btnAbout!.



                       Bài tập thực hành Chuyên đề Visual Studio .NET               28
private void btnTask_Click(object sender, EventArgs e)
      {
         string stTask = (sender as Button).Text; 2
         if (stTask == "Close")
            this.Close();
         else if (stTask == "About")
            MessageBox.Show("Day la chuong trinh minh hoa", "Thong bao");
      }

    15. Bây giờ, chúng ta tinh chỉnh thêm để chương trình hỗ trợ hiệu ứng mouse hover
        cho tất cả các điều khiển trong form:
            a. Sửa lại phần mã nguồn cho 2 phương thức xử lý sự kiện
               btn_MouseEnter và btn_MouseLeave như sau:
              private void btn_MouseEnter(object sender, EventArgs e)
              {
                 (sender as Control).ForeColor = Color.Red;
              }

              private void btn_MouseLeave(object sender, EventArgs e)
              {
                 (sender as Control).ForeColor = SystemColors.ControlText;
              }

            b. Trong phần FormDesigner, chọn tất cả các đối tượng trên bề mặt Form.
            c. Trong cửa sổ Properties, chọn phương thức xử lý sự kiện MouseLeave
                cho tất cả các đối tượng đang chọn là btn_MouseLeave (xem hình vẽ)




2
 Phép chuyển kiểu (sender as Button) trong câu lệnh này là thành công vì cả btnClose và btnAbout đều là các
điều khiển kiểu Button



                      Bài tập thực hành Chuyên đề Visual Studio .NET             29
d. Làm tương tự để gán phương thức xử lý sự kiện MouseEnter cho tất cả
         các điều khiển nói trên là btn_Enter.
      e. Chạy chương trình để xem hiệu ứng: khi rê con trỏ chuột qua các điều
          khiển, font chữ của chúng sẽ được đổi thành màu đỏ.
16. Trong bước 11, chúng ta đã biết được cách thức đưa một thành phần điều khiển
    vào giao diện của một Windows Form thông qua mã lệnh (bằng cách tìm hiểu
    phần mã sinh ra bởi Form Designer). Bây giờ, chúng ta sẽ áp dụng để thực hiện
    thêm các điều khiển vào Form và gán phương thức xử lý sự kiện cho chúng
    trong thời gian thực thi chương trình
      a. Bổ sung vào Form một nút bấm btnCreateButton




      b. Cài đặt phương thức xử lý sự kiện Click cho nút bấm này như sau:




              Bài tập thực hành Chuyên đề Visual Studio .NET   30
c. Chạy chương trình và quan sát kết quả.




       Bài tập thực hành Chuyên đề Visual Studio .NET   31
Mở rộng
  - Hãy tìm hiểu ý nghĩa của việc cài đặt mã lệnh ở bước 15.a: (sender as Control).
     Có thể sử dụng phép ép kiểu nào khác không? Tại sao?
   -   Điều chỉnh trong giao diện chương trình, trong đó có một số điều khiển (Label,
       TextBox, RadioButton, CheckBox hoặc Button) sử dụng màu khác với màu
       mặc định (là SystemColors.ControlText). Khi đó, hiệu ứng mouse hover hoạt
       động không đúng nữa. Hãy chỉnh sửa chương trình để khắc phục phát sinh này.




                 Bài tập thực hành Chuyên đề Visual Studio .NET   32
Bài thực hành 2.2 usingControls

Tóm tắt
Xây dựng chương trình điền thông tin cá nhân như minh họa




Kỹ thuật được trình bày
  - Giới thiệu một ứng dụng WinForms cơ bản
   -   Cách thức lưu file với nội dung tiếng Việt
   -   Các thành phần điều khiển cơ bản: Button, Label, TextBox, PictureBox, Timer,
       …
   -   Nạp một ảnh từ file

Trình tự thực hiện
   1. Tạo mới một project loại Windows Application, đặt tên là usingControls
   2. Theo mặc định, một lớp Form1 được sinh ra. Chỉnh sửa các thuộc tính của
      Form1 với các giá trị như bảng dưới:
        Thuộc tính           Giá trị                  Ghi chú
        Name                 FormMain
        Text                 Hello WinForms           Tiêu để của cửa sổ
        FormBorderStyl       FixedSingle              Kích thước của cửa sỗ sẽ không được
        e                                             thay đổi khi chạy chương trình
        MaximizeBox          False                    Vô hiệu hóa nút Maximize của cửa
                                                      sổ


                  Bài tập thực hành Chuyên đề Visual Studio .NET     33
Chú ý rằng, những thuộc tính có thay đổi giá trị so với mặc định sẽ được hiển
   thị trong cửa sổ Properties dưới dạng chữ in đậm
3. Thiết kế giao diện của form như minh họa. Lưu ý, với mỗi điều khiển bạn đưa
   vào form, nếu dự định truy xuất nó trong phần mã nguồn khi lập trình thì hãy
   đặt tên nó thay vì để như tên mặc định.




   Chỉnh sửa thuộc tính của một số đối tượng như sau:
    Điều khiển      Thuộc tính                             Giá trị
    dtpDOB          Format                                 Custom
                    CustomFormat                           dd/MM/yyyy
    txtOther        Enable                                 False
    lblInfo         Font                                   Chọn font thích hợp, in đậm
    picImage        SizeMode                               StretchImage
    lblName         BackColor                              Transparent (Web)
    tmrScroll       Interval                               120


4. Nhấn Ctrl + S để lưu nội dung project. Do chúng ta có sử dụng ký tự tiếng Việt
   trong Form nên Visual Studio có hiển thị hộp thoại để yêu cầu chỉ định bảng mã
   lưu ký tự:



               Bài tập thực hành Chuyên đề Visual Studio .NET        34
Nhấn nút “Save With Other Encoding” để chọn bảng mã thích hợp – sau đó bạn
   có thể chọn cách lưu theo UTF8 như hình dưới (cũng có thể chọn tùy chọn
   Unicode – Codepage 1200):




5. Cài đặt phần mã lệnh cho sự kiện Click của nút bấm btnSelectImage như sau:




   Khi người sử dụng nhấn vào nút này, một hộp thoại sẽ hiện ra cho phép chọn
   ảnh. Chỉ các tập tin có phần mở rộng là BMP, JPG, GIF mới được hiển thị để
   lựa chọn. Điều này được thiết lập thông qua thuộc tính Filter của đối tượng
   dlgOpen (thuộc lớp OpenFileDialog).




              Bài tập thực hành Chuyên đề Visual Studio .NET   35
6. Khi người sử dụng gõ tên của họ vào txtName thì nội dung của lblName cũng
   thay đổi theo. Muốn vậy, ta cài đặt mã lệnh cho sự kiện TextChanged của
   txtName như (1) – xem minh họa code ở dưới
7. Đối tượng txtOther chỉ được sử dụng (Enabled) khi mà chkOther được check
   vào, do đó ta cũng cài đặt mã lệnh cho sự kiện CheckChanged của chkOther
   như (2)
8. Khi nhấn nút “Cập nhật” thì nội dung của lblInfo được cập nhật theo như phần
   mã lệnh cài đặt cho sự kiện Click của btnUpdate (3)
9. Người sử dụng có thể bật tắt chế độ cuộn nội dung dòng chữ lblInfo bằng cách
   nhấn chuột vào nó. Cài đặt mã lệnh cho sự kiện Click của lblInfo như (5)
10. Để cuộn nội dung dòng chữ, cài đặt mã lệnh cho sự kiện Tick của tmrScroll như
    (4)




              Bài tập thực hành Chuyên đề Visual Studio .NET   36
Bài thực hành 2.3 textFormat

Tóm tắt
Xây dựng chương trình thể hiện định dạng cho đoạn văn bản tĩnh (Label)

Kỹ thuật được trình bày
  - Cách sử dụng Font, FontStyle trong ứng dụng Windows Form
   -   Truy xuất các thành phần dữ liệu giữa các Form
   -   Sử dụng cửa sổ dạng Dialog trong chương trình

Trình tự thực hiện
   1. Tạo mới một ứng dụng loại Windows Applications, đặt tên là textFormat
   2. Theo mặc định, một lớp Form1 được sinh ra. Chỉnh sửa các thuộc tính của
      Form1 với các thuộc tính giá trị như hình dưới:
        Thuộc tính         Giá trị                   Ghi chú
        Name               FormMain
        Text               Text formartting          Tiêu để của cửa sổ
        FormBorderStyl     FixedSingle               Kích thước của cửa sỗ sẽ không được
        e                                            thay đổi khi chạy chương trình
        MaximizeBox        False                     Vô hiệu hóa nút Maximize của cửa
                                                     sổ
       Chú ý rằng, những thuộc tính có thay đổi giá trị so với mặc định sẽ được hiển
       thị trong cửa sổ Properties dưới dạng chữ in đậm
   3. Thiết kế giao diện cho FormMain như hình dưới đây




        STT Thuộc tính             Giá trị             Ghi chú
         1     Name                lblAdvert           Label
         2     Name                pnlAdvert           Panel
         3     Name                btnChangeText       Button
               Text                ChangeText…



                 Bài tập thực hành Chuyên đề Visual Studio .NET     37
4    Name               btnClose              Button
           Text               Close


4. Bổ sung vào Project thêm một WindowForm bằng cách chọn menu lệnh Project
    Add Windows Forms…




   Chọn tên file của Windows Form cần tạo là FormSettings.cs như hình rên.


5. Thiết kế giao diện cho FormSettings vừa được tạo ra như hình dưới.




   Trình tự đặt các điều khiển vào form có thể thực hiện như sau:



              Bài tập thực hành Chuyên đề Visual Studio .NET   38
-   Điều khiển TextBox đặt tên là txtAdvert.
   -   Điều khiển Frame với thuộc tính Text là “Format”
   -   4 CheckBox có tên là chkB, chkI, chkU, chkS với thuộc tính Text được thiết
       lập tương ứng là “Bold”, “Italic”, “Underline” và “Strike out”
   -   4 RadioButton có tên là rbRed, rbGreen, rbBlue, rbYellow với thuộc tính
       Text được thiết lập tương ứng với 4 tên màu “Red”, “Green”, “Blue” và
       “Yellow”
   -   3 nút bấm btnOK, btnApply, btnCancel. Sau đó thiết lập thuộc tính
       DialogResult của các nút bấm btnOK, btnCancel lần lượt là OK và Cancel




6. Tiến hành cài đặt mã lệnh cho nút bấm btnChangeText ở FormMain như sau:
  private void btnChangeText_Click(object sender, EventArgs e)
  {
     FormSettings frm = new FormSettings();
     if (frm.ShowDialog() == DialogResult.OK)
     {
           lblAdvert.Text = “You’ve clicked at OK button!”;
     }
  }

7. Thực hiện chạy chương trình và quan sát kết quả. Để ý rằng, với việc thiết lập
   thuộc tính DialogResult cho 2 nút bấm btnOK và btnCancel như bước 5 ở trên,
   chúng ta không cần cài đặt mã lệnh cho 2 nút này mà vẫn có hiệu ứng frm (một
   thể hiện của FormSettings) bị đóng khi một trong 2 nút này được nhấn. Hơn
   nữa, chúng ta sẽ biết được người sử dụng đã nhấn vào nút nào bằng cách kiểm
   tra kết quả của hàm frm.ShowDialog() như trên.



              Bài tập thực hành Chuyên đề Visual Studio .NET   39
Trong Bước tiếp theo, chúng ta sẽ làm cho hai form sẽ tương tác dữ liệu được
   với nhau trong thời gian thực thi.
8. Trong FormMain, tạo ra một thuộc tính AdvertText kiểu string như sau:
      public string AdvertText
      {
        get
        {
           return lblAdvert.Text;
        }

          set
          {
             lblAdvert.Text = value;
          }
      }

9. Trong FormSettings, chúng ta thêm khai báo biến thành phần và sửa lại phương
   thức khởi dựng của lớp:
  public partial class FormSettings: Form
  {
    FormMain formMain;

      public FormSettings(FormMain frmMain)
      {
        InitializeComponent();
        this.formMain = frmMain;
      }

      private void FormSettings_Load(object sender, EventArgs e)
      {
          this.txtAdvert.Text = formMain.AdvertText;
      }

      .....
      .....
  }

10. Cài đặt phương thức xử lý sự kiện Click của nút bấm btnApply như sau
          public void btnApply_Click(object sender, EventArgs e)
      {
          formMain.AdvertText = this.txtAdvert.Text;
      }

   Để ý rằng, bạn phải chỉnh sửa modifier của phương thức bntApply_Click thành
   public thay vì private như FormDesigner sinh ra theo mặc định. Điều này cho


                 Bài tập thực hành Chuyên đề Visual Studio .NET   40
phép phương thức này có thể được triệu gọi từ ngoài lớp FormSettings như ở
   bước 11 dưới đây.
11. Chỉnh sửa lại phương thức xử lý sự kiện Click của nút bấm btnChangeText ở
    FormMain như sau:
     private void btnChangeText_Click(object sender, EventArgs e)
     {
        FormSettings frm = new FormSettings(this);
        if (frm.ShowDialog() == DialogResult.OK)
        {
           frm.btnApply_Click(null, null);
        }
     }
12. Chạy thử chương trình và quan sát kết quả. Tóm lại, chúng ta đã thực hiện
    những bước sau đây để có thể thay đổi dữ liệu từ 2 form:
        a. Tạo property (AdvertText) có modifier kiểu public cho phần dữ liệu
           muốn truy xuất (lblAdvert.Text)
        b. Tìm cách truyền tham chiếu của form chứa property nói trên
           (FormMain) đến form muốn truy xuất (FormSettings)
13. Tiếp theo, chúng ta sẽ tạo ra property tên AdvertForeColor có kiểu Color trong
   lớp FormMain để thực hiện thay đổi màu sắc của lblAdvert:
    public Color AdvertForeColor
    {
      get
      {
         return lblAdvert.ForeColor;
      }
      set
      {
         lblAdvert.ForeColor = value;
      }
    }

14. Cập nhật lại phương thức xử lý sự kiện Click của nút bấm btnApply và sự kiện
    Load trong lớp FormSettings:
    private void FormSettings_Load(object sender, EventArgs e)
    {
       this.txtAdvert.Text = formMain.AdvertText;

        rbRed.Checked = formMain.AdvertForeColor == Color.Red;
        rbGreen.Checked = formMain.AdvertForeColor == Color.Green;
        rbBlue.Checked = formMain.AdvertForeColor == Color.Blue;
        rbYellow.Checked = formMain.AdvertForeColor == Color.Yellow;
    }



               Bài tập thực hành Chuyên đề Visual Studio .NET   41
public void btnApply_Click(object sender, EventArgs e)
    {
      formMain.AdvertText = this.txtAdvert.Text;

       if (rbRed.Checked)
          formMain.AdvertForeColor = Color.Red;
       else if (rbGreen.Checked)
          formMain.AdvertForeColor = Color.Green;
       else if (rbBlue.Checked)
          formMain.AdvertForeColor = Color.Blue;
       else
          formMain.AdvertForeColor = Color.Yellow;
      }
15. Như vậy, trong các bước trên, chúng ta đã tạo ra một property kiểu Color và
    đồng bộ hóa nó với 4 điều khiển RadioButton. Những bước tiếp theo chúng ta
    tạo ra thêm một property khác để thay đổi định dạng font chữ cho lblAdvert.
    Trước hết, chúng ta cần biết
      a. Các thuộc tính như Bold, Italic, .. của lblAdvert.Font là chỉ đọc. Thế nên
         không thể thực hiện phép gán để thay đổi lblAdvert.Font.Bold được
      b. Việc thay đổi tính chất Bold, Italic, … của một đối tượng Font được thực
         hiện bằng cách tạo mới đối tượng Font (tham khảo thêm MSDN để biết
         13 hàm nạp chồng để khởi tạo một đối tượng Font)
      c. Một trong các hàm nạp chồng khá đơn giản mà chúng ta có thể sử dụng
         để tạo một đối tượng Font có cú pháp là:


         Trong đó, family sẽ là tên font, emSize là kích cỡ font, style là kiểu font.
         Giá trị của style sẽ là sự tổng hợp theo phép toán or của các giá trị
         FontStyle.Bold,       FontStyle.Italic,      FontStyle.Underline         và
         FontStyle.Strikeout.
16. Tạo property AdvertFontFormat trong lớp FormMain như sau:
    public bool[] AdvertFontFormat
    {
      get
      {
         Font f = lblAdvert.Font;
         return (new bool[] {f.Bold, f.Italic, f.Underline, f.Strikeout});
      }
      set
      {
         if (value.Length == 4)
         {
            FontStyle fs = FontStyle.Regular;


              Bài tập thực hành Chuyên đề Visual Studio .NET     42
if (value[0])
                   fs = fs | FontStyle.Bold;
                if (value[1])
                   fs = fs | FontStyle.Italic;
                if (value[2])
                   fs = fs | FontStyle.Underline;
                if (value[3])
                   fs = fs | FontStyle.Strikeout;
                lblAdvert.Font = new Font(lblAdvert.Font.Name,
                   lblAdvert.Font.Size, fs);
            }
        }
    }

  Property AdvertFontFormat có kiểu dữ liệu là một mảng bool gồm 4 phần tử
  mà thứ tự của chúng sẽ tương ứng biểu diễn tính chất Bold, Italic, Underline và
  Strikeout của một FontStyle.
17. Tiếp đến, chúng ta sẽ cập nhật các phương thức cần thiết trong lớp
    FormSettings để sử dụng thuộc tính AdvertFontFormat vừa tạo như sau:
    private void FormSettings_Load(object sender, EventArgs e)
    {
       this.txtAdvert.Text = formMain.AdvertText;

        rbRed.Checked = formMain.AdvertForeColor == Color.Red;
        rbGreen.Checked = formMain.AdvertForeColor == Color.Green;
        rbBlue.Checked = formMain.AdvertForeColor == Color.Blue;
        rbYellow.Checked = formMain.AdvertForeColor == Color.Yellow;

        this.chkB.Checked = formMain.AdvertFontFormat[0];
        this.chkI.Checked = formMain.AdvertFontFormat[1];
        this.chkU.Checked = formMain.AdvertFontFormat[2];
        this.chkS.Checked = formMain.AdvertFontFormat[3];
    }

    public void btnApply_Click(object sender, EventArgs e)
    {
      formMain.AdvertText = this.txtAdvert.Text;

        if (rbRed.Checked)
           formMain.AdvertForeColor = Color.Red;
        else if (rbGreen.Checked)
           formMain.AdvertForeColor = Color.Green;
        else if (rbBlue.Checked)
           formMain.AdvertForeColor = Color.Blue;
        else
           formMain.AdvertForeColor = Color.Yellow;



                  Bài tập thực hành Chuyên đề Visual Studio .NET   43
formMain.AdvertFontFormat = new bool[] {
              chkB.Checked, chkI.Checked, chkU.Checked, chkS.Checked };
       }

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

Mở rộng
  - Qua quá trình cài đặt, có thể thấy, Form Designer của Visual Studio .NET
     không hỗ trợ việc tạo mảng điều khiển giống như ở Visual Basic. Tuy nhiên,
     chúng ta có thể mô phỏng mảng điều khiển bằng cách tạo ra các Property hoặc
     là Indexer để ánh xạ đến các điều khiển.




                 Bài tập thực hành Chuyên đề Visual Studio .NET   44
-

Bài thực hành 2.4 myCalculator

Tóm tắt
Xây dựng chương trình mô phỏng một máy tính điện tử đơn giản.

Kỹ thuật được trình bày:
  - Xây dựng ứng dụng GUI với WinForms

   -   Cách thức cài đặt, gắn kết, xử lý events với một thành phần giao diện

   -   Tạo hiệu ứng transparent với Form

Trình tự thực hiện
   1. Tạo mới một project loại Windows Application, đặt tên là myCalculator
   2. Theo mặc định, một lớp Form1 được sinh ra. Chỉnh sửa các thuộc tính của
      Form1 với các giá trị như bên dưới:
        Thuộc tính          Giá trị                   Ghi chú
        Name                FormMain
        Text                my Calculator             Tiêu để của cửa sổ
        FormBorderStyl      FixedSingle               Kích thước của cửa sỗ sẽ không được
        e                                             thay đổi khi chạy chương trình
        MaximizeBox         False                     Vô hiệu hóa nút Maximize của cửa
                                                      sổ
   3. Tạo giao diện cho chương trình như hình minh họa




       Sau khi đã tạo ra được giao diện với các điều khiển có vị trí, kích thước hợp lý,
       bạn nên cố định chúng. Thực hiện điều này bằng cách nhắp phải chuột lên bề
       mặt của FormMain, trong menu hiện ra, chọn Lock Controls. Điều chỉnh các
       thuộc tính của đối tượng “màn hình hiển thị” như sau:



                  Bài tập thực hành Chuyên đề Visual Studio .NET     45
Đối tượng                  Thuộc tính                  Giá trị
    “Màn hình hiển thị”        Name                        txtScreen
                               Text                        0
                               Font                        Chọn font thích hợp
                               ReadOnly                    True
                               TextAlign                   Right


4. Trước khi bắt tay vào viết mã lệnh, bạn phải hình dung cách thức làm việc của
   chương trình cũng như việc chuẩn bị các kiểu đối tượng, dữ liệu.
      Giao diện của chương trình chỉ là các nút bấm và “màn hình” hiển thị kết
   quả. Do chỉ là máy tính đơn giản nên chương trình chỉ hỗ trợ các phép tính 2
   ngôi cơ bản. Có thể phân biệt nút bấm thuộc các nhóm:
      (1) Nút bấm chữ số 0, 1, …, 9, ký hiệu dấu chấm thập phân, nút đảo dấu, nút
         xóa trái  xác định hai toán hạng tham gia vào phép tính.
      (2) Nút bấm phép tính +, -, x, /  xác định phép tính
      (3) Nút Enter  để kích hoạt việc thực hiện phép tính và hiển thị kết quả
      (4) Nút C  khởi tạo một phép toán mới
   Trình tự các bước sử dụng của máy tính thông thường như sau: Người sử dụng
   “nhập vào” số thứ nhất, xác định phép toán, sau đó “nhập vào” số thứ hai, cuối
   cùng nhấn dấu =

       Khái niệm “nhập vào” ở đây có nghĩa là người sử dụng dùng liên tiếp các
   nút bấm thuộc nhóm (1). Chú ý rằng dấu chấm thập phân chỉ xuất hiện 1 lần
   trong 1 số hạng. Việc xác định phép toán có thể xác định nhiều lần giữa hai lần
   nhập hai số hạng. Tóm lại, khi người sử dụng nhấn một nút bấm trong nhóm
   (1), ta luôn biết được anh ta đang “nhập số liệu” cho số hạng nào: nếu chưa xác
   định phép toán thì có nghĩa anh ta đang nhập cho số thứ nhất, ngược lại thì đang
   nhập cho số thứ hai.

       Điều khác nữa cần phải tính đến, đó là lưu trữ dữ liệu như thế nào? Trong
   bài toán này, việc sử dụng string để lưu trữ các số hạng là hợp lý cho việc nhập
   số. Phép tính sẽ được lưu trữ bằng một ký tự.

5. Ngay trong đầu phần mã nguồn partial class FormMain: Form, bổ sung khai
   báo các biến
      a. stNum1, stNum2 kiểu string để đại diện cho hai chuỗi số hạng.
      b. op kiểu char để đại diện cho phép toán (op là viết tắt của operator)
6. Cài đặt phần mã lệnh cho sự kiện FormLoad và sự kiện click của nút C như sau:



              Bài tập thực hành Chuyên đề Visual Studio .NET         46
7. Các nút bấm trong nhóm (1) được cài đặt phần mã lệnh tương ứng với sự kiện
   click như sau:




8. Phương thức xử lý sự kiện click của nút bấm thuộc nhóm (2) – xác định phép
   tính




9. Cuối cùng, phương thức xử lý sự kiện click của nút bấm ‘=’ được cài đặt như
   sau:




             Bài tập thực hành Chuyên đề Visual Studio .NET   47
10. Đến đây, các tính năng cơ bản của máy tính đã được cài đặt. Chúng ta khảo sát
   thêm một số hiệu ứng đặc biệt của lớp đối tượng Form:
      a. Thiết lập thuộc tính TopMost bằng True để trong thời gian thực thi, cửa
         sổ chương trình luôn luôn nổi.
      b. Bổ sung phương thức xử lý sự kiện Activated và Deactive cho
          FormMain. Hai sự kiện này được kích hoạt tương ứng khi người dùng
          chuyển focus từ vào ứng dụng (Activated) hay ra khỏi ứng dụng
          (Deactive)




      c. Minh họa khi chạy chương trình: khi người sử dụng chuyển focus ra
         khỏi cửa sổ ứng dụng, cửa sổ này sẽ bị mờ đi 50%.




              Bài tập thực hành Chuyên đề Visual Studio .NET   48
Mở rộng
  - Cài đặt thêm các tính năng khác cho máy tính, chẳng hạn hỗ trợ phép tính 1
     ngôi như căn bậc 2, nghịch đảo, phần trăm,… Chú ý phần xử lý lỗi (ví dụ như
     chia cho 0, căn bậc hai với số âm…)
   -   Hỗ trợ để người sử dụng có thể nhập dữ liệu bằng bàn phím.




                 Bài tập thực hành Chuyên đề Visual Studio .NET   49
Bài thực hành 2.5 myNotePAD

Tóm tắt
Chương trình có các chức năng cơ bản của một chương trình soạn thảo văn bản đơn
giản (như Ms Windows NotePAD): Tạo mới, Soạn thảo, Lưu lên đĩa, Mở từ đĩa.
Chương trình cũng cho phép người sử dụng thiết lập các cấu hình phụ như: font chữ
thể hiện (loại font, kích thước, màu sắc, ...).

Kỹ thuật được trình bày
  - Sử dụng menu
   -   Xây dựng ứng dụng với nhiều form
   -   Lấy danh sách font hệ thống
   -   Cách liên hệ giữa các đối tượng thành phần thuộc các form, module khác nhau

Trình tự thực hiện
   1. Tạo mới một project kiểu Windows Application, đặt tên là myNotePAD
   2. Đổi thuộc tính Name của form thành FormMain
   3. Bổ sung các thành phần StatusBarStrip, MenuStrip với các menu lệnh như
      minh họa.




   4. Bổ sung thêm thành phần TextBox với tên gọi là txtDoc. Thiết lập các thuộc
      tính của txtDoc như sau:
        Thuộc tính             Giá trị
        Multiline              True
        Dock                   Fill



                    Bài tập thực hành Chuyên đề Visual Studio .NET      50
5. Khai báo một biến fileName kiểu string ở đầu phần mô tả lớp. Biến này sẽ lưu
   tên file đang được soạn thảo. Sau đó cài đặt phương thức xử lý sự kiện cho tất
   cả các ToolStripMenuItem như sau:




    Phần xử lý cho “else if (stTask == “Configuration…”)…” sẽ được cài đặt
   sau.




             Bài tập thực hành Chuyên đề Visual Studio .NET   51
Chú ý rằng thuộc tính Text của các ToolStripMenuItem được sử dụng để viết
   các câu lệnh if ở trên. Vậy nên, bạn phải nhớ chính xác các thuộc tính Text của
   chúng để viết mã lệnh đúng.
   Thực ra, bạn có thể viết riêng rẽ cho từng phương thức xử lý sự kiện Click
   của từng ToolStripMenuItem, tuy nhiên theo ý kiến của riêng người viết giáo
   trình này, cách gộp chung như ở trên giúp bạn tập trung mã lệnh hơn.
6. Bước tiếp theo chúng ta cài đặt phần mã lệnh cho phép người sử dụng thay đổi
   font hiển thị tài liệu:
   Bổ sung một Form vào project bằng cách nhấn tổ hợp phím Ctrl + Shift + A.
   Một hộp thoại như sau hiện ra:




   Chọn loại đối tượng cần thêm là Windows Form, gõ tên file là “FormConfig.cs”
   rồi nhấn Add.
   Sau thao tác này, Visual Studio sẽ tạo ra một file trong đó có khai báo lớp
   FormConfig (trùng tên với tên file mà bạn cung cấp). Nếu muốn, chúng ta có
   thể đổi tên lớp của form này.
7. FormConfig sẽ làm nhiệm vụ hiển thị danh sách các fonts hiện có trong hệ
   thống để người sử dụng chọn. Thiết kế giao diện của FormConfig như sau:




              Bài tập thực hành Chuyên đề Visual Studio .NET   52
Dự kiến, khi form này bắt đầu nạp nó sẽ hiển thị
                                    danh sách tất cả các fonts đang được cài đặt trong
                                    hệ thống vào lstFonts. Font đang được sử dụng ở
                                    txtDoc (ở FormMain) sẽ được tô sáng trong
                                    lstFonts. Cuối cùng, khi người sử dụng nhấn nút
                                    OK, font được chọn trong lstFonts sẽ được sử dụng
                                    cho txtDoc. Như vậy, đã có sự sử dụng biến thành
                                    phần giữa hai forms: FormMain và FormConfig.




8. Trước hết, thiết lập thuộc tính DialogResult của hai nút OK và Cancel là OK và
   Cancel. Có thể thiết lập thêm các thuộc tính Anchor của các đối tượng để có
   giao diện hợp lý.
9. Thiết lập thuộc tính Modifiers của lstFonts là Public. Bằng cách này, chúng ta
   có thể truy xuất đến thành phần lstFonts từ bên ngoài.
10. Bổ sung biến thành phần stFontName kiểu string vào khai báo FormConfig,
   xem (1).
11. Sửa phương thức khởi dựng của lớp FormConfig như (2).
12. Viết mã cho phương thức xử lý sự kiện Load của form như (3).




13. Hoàn thiện phần gọi FormConfig ở FormMain như sau:




              Bài tập thực hành Chuyên đề Visual Studio .NET   53
Mở rộng
  - Cài đặt thêm các chức năng thường dùng khác đối với ứng dụng soạn thảo văn
       bản
   -   Sửa lại ứng dụng theo giao diện MDI




                 Bài tập thực hành Chuyên đề Visual Studio .NET   54
Bài thực hành 2.6 Quản lý sinh viên - WinForms version

Tóm tắt
Thực hiện lại chương trình quản lý sinh viên như bài thực hành 1.7 nhưng giao tiếp với
người sử dụng thông qua giao diện Windows.




Chương trình có thể tạo mới, mở và lưu tập tin chứa dữ liệu về các sinh viên có định
dạng như ví dụ sau:

Nguyễn Văn Trung|True|1981/10/25 00:00:00|9|10|10
Trịnh Phương|False|1984/11/20 00:00:00|4|9
Trần Văn Phúc|True|1999/10/01 00:00:00|6|6.5|6.5|8
Đặng Phương Nam|True|1991/10/25 00:00:00|2|10
Trịnh Phương Lan|False|1994/11/20 00:00:00|2|9


Theo định dạng lưu này, mỗi dòng thông tin sẽ chứa các thông tin Họ tên, Giới tính,
Ngày sinh, sau đó là các cột điểm. Nếu có 3 (tương ứng 2, 4) cột điểm thì sinh viên đó
thuộc chuyên ngành CNTT (tương ứng Văn, Vật lý). (Xem lại Bài thực hành 1.7 để
biết thêm về mô tả thông tin sinh viên).

Khi người sử dụng chọn một sinh viên trong danh sách (danh sách này chỉ hiển thị tên
sinh viên) thì các thông tin chi tiết về sinh viên này sẽ được hiển thị và cho phép chỉnh
sửa tương ứng (xem hình vẽ). Chú ý rằng, tùy theo chuyên ngành của sinh viên, tên
các môn học sẽ được hiển thị một cách hợp lý.




                    Bài tập thực hành Chuyên đề Visual Studio .NET        55
Kỹ thuật được trình bày
  - Sử dụng kỹ thuật hướng đối tượng trên ứng dụng GUI
   -   Truy xuất file có định dạng quy ước sẵn

Trình tự thực hiện
   1. Tạo form có giao diện như minh họa. Đặt tên các thành phần cho hợp lý




   2. Thiết kế các lớp đối tượng sinh viên như sơ đồ lớp được mô tả ở dưới. Các
       phương thức, thuộc tính của các lớp này đã được xây dựng trong các bài thực
       hành trước, ở đây ta chỉ thảo luận thêm về hàm ToFormatText() ở các lớp:




                  Bài tập thực hành Chuyên đề Visual Studio .NET   56
Trong lớp SinhVien, đặc tả hàm này như là một hàm ảo và không cho sử dụng
trực tiếp từ thể hiện thuộc lớp SinhVien như sau:




Đối với lớp SinhVienCNTT, hàm này được override lại thành:




Tương tự, đối với lớp SinhVienVan và SinhVienVL, hàm này lần lượt được cài
đặt như sau:




          Bài tập thực hành Chuyên đề Visual Studio .NET   57
3. Xử lý sự kiện mở file dữ liệu, nạp vào listBox:
      o Bổ sung biến thành phần fileName để chứa tên file dữ liệu
      o Bổ sung thêm phương thức void updateFormTitle()
      o Thêm sự kiện Click cho nút Open như sau:




   Chú ý rằng, các lớp SinhVienVan, SinhVienCNTT, SinhVienVL đều phải
   override hàm ToString() để được hiển thị ngắn gọn trong hộp danh sách.
4. Xử lý sự kiện tạo mới file dữ liệu



              Bài tập thực hành Chuyên đề Visual Studio .NET   58
a. Thêm phương thức xử lý sự kiện Click của nút New




      b. Gán phương thức xử lý sự kiện này cho sự kiện Load của Form
5. Khi người sử dụng chọn 1 sinh viên trong lstSinhVien, thông tin chi tiết về sinh
   viên này sẽ được hiển thị. Viết phương thức xử lý sự kiện
   SelectedIndexChanged của lstSinhVien như sau:




6. Viết phương thức xử lý sự kiện Click của nút “Cập nhật”, “Xóa” và “Bỏ cập
   nhật” như (1), (2), (3):




               Bài tập thực hành Chuyên đề Visual Studio .NET   59
7. Cài đặt phương thức xử lý sự kiện Click cho 4 nút “|<”, “<”, “>”, “>|” như sau:




8. Tiếp đến, cài đặt phương thức xử lý sự kiện Click của nút bấm Save và Save
   as…




              Bài tập thực hành Chuyên đề Visual Studio .NET   60
Mở rộng
  - Thêm phần xử lý lỗi cho chương trình
   -   Bổ sung chức năng Tạo mới Sinh viên




                 Bài tập thực hành Chuyên đề Visual Studio .NET   61
Bài thực hành 2.7 myFileViewer

Tóm tắt
Trong bài thực hành này, bạn sẽ được tiếp cận một số kỹ thuật nâng cao như: kế thừa
form, thành phần điều khiển TreeView, ListView, ... Ứng dụng cuối cùng được tạo ra
sẽ là ứng dụng quản lý tập tin kiểu ACDSee.




Kỹ thuật được trình bày
  - Thừa kế form
   -   Làm quen với các component, các thư viện nâng cao

Trình tự thực hiện
   1. Tạo mới một ứng dụng kiểu Windows Application với tên là fileViewer. Đặt
      tên cho form chính là FormMain.
   2. Thiết kế giao diện ban đầu cho FormMain như hình vẽ. Khi chạy chương trình,
       vùng bên tay trái sẽ hiển thị cây thư mục (đối tượng tvwDirs). Mỗi khi chọn 1
       thư mục ở cây, nội dung của nó được hiển thị ở lvwItems bên phải. Ngay phía
       dưới tvwDirs là vùng “preview” nội dung của file được chọn trong lvwItems.




                 Bài tập thực hành Chuyên đề Visual Studio .NET   62
3. Thêm vào Form một đối tượng ImageList có tên là imageList1 với các ảnh như
   hình dưới:




4. Gán imageList1 cho thuộc tính ImageList của tvwDirs và lvItems.
5. Viết mã lệnh cho sự kiện Load của FormMain như (1)




                Bài tập thực hành Chuyên đề Visual Studio .NET   63
Trước hết, lấy danh sách các ổ đĩa của hệ thống. Ứng với mỗi đĩa như vậy, ta sẽ
   thêm tất cả các thư mục con cấp 1 của ổ đĩa (hãy giải thích?) bằng cách gọi
   phương thức (2). Để ý rằng (2) sẽ được gọi đệ quy.
6. Khi người dùng nhấn vào dấu “+” của một node trên treeview để mở nhánh này
   ra, ta phải hiển thị được tất cả các thư mục con của nó. Việc này được chuẩn bị
   qua phương thức xử lý sự kiện BeforeExpand của treeview tvwDirs:




7. Khi người sử dụng chọn một node trên cây thì nội dung của thư mục tương ứng
   (bao gồm tất cả thư mục và files chứa trong nó) sẽ được hiển thị trong listview
   lvItems.
   Để thuận tiện cho những thao tác khác sau này của ứng dụng, chúng ta quy ước,
   trong lvItems, phần tử đại diện cho file sẽ có ImageIndex là 2, còn phần tử đại
   diện cho thư mục sẽ có ImageIndex là 0.
   Chúng ta xử lý sự kiện AfterSelect của tvwDirs như sau:




              Bài tập thực hành Chuyên đề Visual Studio .NET   64
Chú ý rằng, ngoài các thư mục và tập tin, ta thêm vào một “thư mục” đặc biệt,
   thư mục “..” đại diện cho thư mục cha của thư mục hiện tại.
8. Tiếp đến, chúng ta viết mã cho sự kiện nhấn đúp chuột vào một phần tử trong
   listview. Có 3 khả năng xảy ra:
   (1) người sử dụng nhấn đúp vào phần tử là thư mục “..”  chuyển đến thư mục
      trên cấp của thư mục hiện tại
   (2) người sử dụng nhấn đúp vào phần tử thư mục bình thường  chọn thư mục
      đó làm thư mục hiện tại
   (3) người sử dụng nhấn đúp vào phần tử là một file  hiển thị nội dung của file
      này ra một cửa sổ riêng một cách thích hợp theo nội dung của nó.
   Trong phần code dưới đây, ta chỉ quan tâm đến 2 khả năng đầu. Khả năng còn
   lại sẽ được hoàn thiện ở các bước sau.




              Bài tập thực hành Chuyên đề Visual Studio .NET   65
9. Tiếp theo, ta sẽ cài đặt thêm chức năng Preview nội dung của phần tử file ảnh
   đang được chọn trong listview lvItems. Sự kiện SelectedIndexChanged của
   lvItems được xử lý như sau:




10. Phần còn lại của bài thực hành sẽ hướng dẫn bạn cài đặt phần xử lý khả năng
   (3) trong bước 8. Giả sử rằng, ứng dụng của chúng ta chỉ hỗ trợ hiển thị ra cửa
   sổ 2 loại nội dung: ảnh và văn bản. Như thế, để hiển thị mỗi loại nội dung,
   chúng ta cần 1 loại form khác nhau. Tuy thế, cả hai loại form này cũng có một
   số thuộc tính và hành vi chung. Ta xác định mối quan hệ của chúng qua sơ đồ
   lớp như sau:



              Bài tập thực hành Chuyên đề Visual Studio .NET   66
Tạo mới một Form để hiển thị nội dung file như sau:




11. Thiết kế giao diện cho FormFile như sau:
      o Đặt một Panel tên pnlButtons neo phía dưới Form (Dock = Bottom).
      o Trên pnlButtons, đặt nút btnClose vào vị trí thích hợp.
      o Có thể trang trí thêm cho pnlButtons, ví dụ đặt một Label trong nó như
        hình minh họa.
      o Phần trống còn lại của form dự kiến sẽ dùng để hiển thị nội dung của
        file.




              Bài tập thực hành Chuyên đề Visual Studio .NET   67
12. Cài đặt mã lệnh cho form như sau
      o Cài đặt phương thức xử lý sự kiện Click của nút btnClose (1)
      o Bổ sung thêm hàm virtual showContent() vào lớp (2). Dự kiến hàm này
          sẽ làm nhiệm vụ hiển thị nội dung của file: tùy theo file ảnh hay file văn
          bản mà có cách hiển thị khác nhau, nhưng thao tác chung nhất là hiển thị
          đường dẫn của file lên tiêu đề cửa sổ.




13. Lưu và Build ứng dụng. (rất quan trọng cho các bước sau!)
14. Bây giờ, ta bổ sung thêm một Windows Form vào project. Tuy nhiên khác với
    các lớp cửa sổ ta đã bổ sung trước đó, lần này ta sẽ bổ sung một Windows Form
    kế thừa từ một lớp ta đã thiết kế. Cụ thể là FormFileImage kế thừa từ FormFile.




              Bài tập thực hành Chuyên đề Visual Studio .NET    68
15. Visual Studio sẽ hiển thị hộp thoại hỏi lớp mà FormFileImage được kế thừa:




   Chọn FormFile làm Form để kế thừa rồi nhấn OK
16. FormFileImage được tạo ra với giao diện như dưới đây:




              Bài tập thực hành Chuyên đề Visual Studio .NET   69
Để ý, các thành phần được kế thừa sẽ từ lớp cha có mũi tên nhỏ bên góc trái.
   Hơn nữa, những thành phần thuộc loại Private thì sẽ có thêm biểu tượng ổ khóa,
   bạn không thể thay đổi thuộc tính của nó trong lớp kế thừa. Ví dụ, nếu muốn
   chỉnh sửa thuộc tính Text của nút btnClose trong FormFileImage, bạn phải thiết
   lập btnClose ở FormFile thuộc loại Protected hoặc Public.
17. Bây giờ, bổ sung thêm một PictureBox có tên là pic vào FormFileImage. Thiết
    lập thuộc tính Dock của nó là Fill. Xem hình minh họa




18. Trong phần mã lệnh cho FormFileImage, ta override hàm showContent như
    sau:




             Bài tập thực hành Chuyên đề Visual Studio .NET   70
19. Tương tự như các bước tạo FormFileImage, ta tạo thêm một Windows Form có
   tên là FormFileText kế thừa từ FormFile có giao diện và phần cài đặt mã lệnh
   như sau:




             Bài tập thực hành Chuyên đề Visual Studio .NET   71
20. Cuối cùng, ta hoàn chỉnh chức năng (3) đã mô tả ở Bước 8 bẳng cách sửa lại
       phương thức xử lý sự kiện DoubleClick của lvItems như sau:




Mở rộng
  - Bổ sung thêm chức năng preview cho file văn bản
   -   Cài đặt chức năng cho các menu




                 Bài tập thực hành Chuyên đề Visual Studio .NET   72
PHẦN 3
                            XỬ LÝ DỮ LIỆU VỚI ADO.NET

       Xử lý dữ liệu là nhiệm vụ phổ biến và quan trọng của nhiều chương trình ứng
dụng. Dữ liệu được truy xuất, xử lý của một chương trình ứng dụng có thể là một tập
tin văn bản, tập tin các bản ghi, hay là một nguồn dữ liệu từ CSDL nào đó. .NET
Framework cung cấp một lượng lớn các thành phần giao diện (Win Forms, Web
Forms) hỗ trợ cho việc trình bày, kết buộc (bind) dữ liệu. Cùng với đó là nền tảng xử
lý dữ liệu ADO.NET cung cấp cách thức làm việc với nhiều loại nguồn dữ liệu khác
nhau một cách linh động. Do tính chất quan trọng của việc xử lý dữ liệu trong một ứng
dụng cùng với sự phức tạp của ADO.NET, trước khi bắt tay vào thực hiện các bài tập
thực hành, chúng ta khảo sát qua một số điểm lý thuyết cơ bản.

Kiến thức cơ bản về ADO.NET 2.0

3.1 Kiến trúc tổng quan của ADO.NET
Kiến trúc của ADO.NET được mô tả như hình dưới, bao gồm hai thành phần chính:
Thành phần truy cập nguồn dữ liệu và thành phần lưu trữ xử lý dữ liệu.




      Thành phần thứ nhất:.NET Framework Data Provider được thiết kế để thực hiện
các thao tác kết nối, gửi các lệnh xử lý đến CSDL (thành phần này còn được gọi với
một tên khác là lớp kết nối – Connectectivity Layer). Trong ADO.NET, có 4 đối tượng
chính với các chức năng cơ bản như sau:

   • Connection: giúp thực hiện kết nối đến các CSDL



                 Bài tập thực hành Chuyên đề Visual Studio .NET   73
• Command: giúp truy cập đến CSDL và thực hiện các phát biểu SQL hay thủ tục
      lưu trữ sẵn (stored procedure) của CSDL
    • DataReader: dùng để đọc nhanh nguồn dữ liệu, chỉ được duyệt tuần tự theo
      chiều tiến của các record
    • DataAdapter: dùng để chuyển dữ liệu truy vấn được cho các đối tượng lưu trữ
      và xử lý (DataSet, DataTable). DataAdapter chủ yếu thực hiện các thao tác như
      SELECT, INSERT, UPDATE, DELETE
    Về mặt thực chất, thành phần .NET Framework Data Provider cung cấp giao diện
lập trình chung để làm việc với các nguồn dữ liệu. Mỗi nhà cung cấp 3 đặc thù sẽ đưa
ra một loại data provider riêng. Dưới đây là bảng mô tả giao diện lập trình cùng với
các lớp cơ bản của các data provider mà ADO.NET cung cấp sẵn:

     Interface           SQL Server            Oracle Provider         OLEDB Provider            ODBC Provider
                           Provider
IDbConnection           SqlConnection         OracleConnection         OledbConnection          OdbcConnection
IDbDataAdapter          SqlDataAdapter        OracleDataAdapter        OledbDataAdapter         OdbcDataAdapter
IDbCommand              SqlCommand            OracleCommand            OledbCommand             OdbcCommand
IDbDataReader           SqlDataReader         OracleDataReader         OledbDataReader          OdbcDataReader


Để sử dụng data provider nào, chúng ta phải tiến hành khái báo using namspace tương
ứng. Chẳng hạn, using System.Data.SqlClient;

      Ngoài những data provider mô tả ở bảng trên, chúng ta có thể reference đến các
data provider khác không được tích hợp sẵn bởi ADO.NET trong Visual Studio .NET,
chẳng hạn như data provider dùng cho MySQL, Postgre, …

      Thành phần thứ hai trong kiến trúc ADO.NET – DataSet – được xem như là
container dùng để lưu trữ đối tượng liên quan đến dữ liệu như DataTable,
DataRelation, DataView. Thành phần này còn được gọi là lớp không kết nối
(disconected layer). DataSet như là một CSDL thu nhỏ tại máy client, có thể chứa các
đối tượng table, view, constaint, ralationship giữa các table, … Tất cả dữ liệu từ nguồn
dữ liệu thực sẽ được nạp vào DataSet dưới dạng các DataTable, đó là một snapshot
của nguồn dữ liệu thực. Khối dữ liệu này sẽ được chỉnh sửa độc lập, sau đó nếu cần sẽ
được cập nhật trở lại nguồn dữ liệu thực. Theo nguyên tắc này, chúng ta không cần
duy trì kết nối liên tục một cách không cần thiết với nguồn dữ liệu thực trong suốt quá
trình thao tác với nó.




3
 Nhà cung cấp ở đây được hiểu theo nghĩa cả về loại nguồn dữ liệu lẫn cách thức truy xuất nguồn dữ liệu. Ví dụ,
ngoài data provider SqlClient do Microsoft cung cấp, cũng có thể có một tổ chức khác phát triển một provider
khác để truy xuất loại nguồn dữ liệu này.



                       Bài tập thực hành Chuyên đề Visual Studio .NET                74
3.2 Tổng quan về các mô hình xử lý dữ liệu trong ADO.NET: Mô hình Kết nối
      (Connected Model) và Mô hình Ngắt Kết nối (Disconnected Model)

3.2.1 Mô hình Kết nối
        Trong mô hình kết nối của ADO.NET, có một connection hoạt động được duy
trì giữa đối tượng DataReader của ứng dụng và một data source (nguồn dữ liệu). Một
dòng dữ liệu (data row) được trả về từ data source mỗi khi phương thức Read của đối
tượng DataReader được thực thi. Điểm quan trọng nhất của mô hình kết nối đó là dữ
liệu được lấy từ tập dữ liệu (các record được trả về bởi một lệnh SQL nào đó) theo
kiểu từng record một cho một lần đọc, chỉ đọc (read-only), và chỉ theo một hướng tiến
(forward-only). Hình dưới đây mô tả cách sử dụng DataReader trong chế độ kết nối.




Các bước điển hình để làm việc với đối tượng DataReader là như sau:

      1. Tạo đối tượng Connection bằng cách truyền một chuỗi Connection string
          cho hàm khởi dựng của nó.
      2. Khởi tạo một biến chuỗi và gán cho câu lệnh SQL dựa theo dữ liệu muốn
         nạp về.
      3. Khởi tạo một đối tượng Command từ với nội dung câu lệnh SQL đã xác
         định ở trên.
      4. Tạo đối tượng DataReader bằng cách thực thi phương thức
         Command.ExecuteReader(). Đối tượng này sau đó sẽ được dùng để đọc kết
         quả của câu truy vấn mỗi dòng một lần.
Đoạn code sau minh họa các bước trên với Data Provider SqlClient. Đoạn code sẽ đọc
danh sách họ tên các sinh viên trong một bảng SinhVien của cơ sở dữ liệu và hiển thị
lên một điều khiển ListBox. Chi tiết về các đối tượng DataReader, Command,
Connection sẽ được đề cập chi tiết sau.

using System.Data.SqlClient;

...

// (1) Tao Connection
SqlConnection cn = new SqlConnection(chuoiKetNoi);

cn.Open();


                 Bài tập thực hành Chuyên đề Visual Studio .NET   75
// (2) Chuoi SQL thuc hien lay danh sach ten cac sinh vien xep tang dan theo
NgaySinh
string sql = "SELECT HoTen FROM SinhVien ORDER BY NgaySinh";

// (3) Tao doi tuong Command
SqlCommand cmd = new SqlCommand(sql, conn);

DbDataReader rdr;

// (4) Tao doi tuong DataReader
rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);

while (rdr.Read())


 listBox1.Items.Add(rdr["HoTen"]); // Fill ListBox

rdr.Close(); // Dong datareader sau khi da su dung xong

Tham số được sử dụng trong phương thức ExecuteReader xác định đối tượng
Connection sẽ được đóng sau khi DataReader được đóng.

3.2.2 Mô hình Ngắt Kết nối
Triết lý của mô hình Ngắt kết nối đó là: Dữ liệu được nạp – sử dụng một lệnh SQL –
từ nguồn dữ liệu bên ngoài vào bộ nhớ đệm tại máy client; tập kết quả được xử lý tại
máy cục bộ; mọi cập nhật sau đó sẽ được truyền từ dữ liệu trong bộ nhớ ngược trở lại
nguồn dữ liệu.

      Mô hình được gọi là “ngắt kết nối” bởi vì đối tượng kết nối chỉ được mở đủ lâu
để đọc dữ liệu từ nguồn dữ liệu và tiến hành các thao tác cập nhật. Bằng cách đưa dữ
liệu về phía máy client, tài nguyên của server – chẳng hạn như thông tin dữ liệu
Connection, bộ nhớ, thời gian xử lý – sẽ được giải phóng bớt. Tuy vậy, mô hình này
cũng có nhược điểm về thời gian cần để nạp tập dữ liệu và bộ nhớ dùng để chứa dữ
liệu tại máy client.

      Như hình dưới đây minh họa, các thành phần chính của mô hình ngắt kết nối đó
là DataApdapter và DataSet. DataAdapter làm nhiệm vụ như là cầu nối giữa nguồn dữ
liệu và DataSet, nạp dữ liệu vào các bảng của DataSet và đẩy các thay đối ngược trở
lại nguồn dữ liệu. Một DataSet đóng vai trò như là một cơ sở dữ liệu quan hệ nằm
trong bộ nhớ, chứa một hay nhiều DataTables, giữa các DataTable này cũng có thể có
các mối quan hệ với nhau như trong một cơ sở dữ liệu quan hệ thực. Một DataTable
chứa các dòng và các cột dữ liệu thường được lấy từ cơ sở dữ liệu nguồn.




                     Bài tập thực hành Chuyên đề Visual Studio .NET   76
Trong số các phương thức và thuộc tính của DataAdapter thì Fill() và Update() là
hai phương thức quan trọng nhất. Fill() chuyển một query đến cơ sở dữ liệu và lưu tập
kết quả trả về trong một DataTable nào đó; phương thức Update() thực hiện một thao
tác thêm, xóa, cập nhật dựa trên những thay đối của đối tượng DataSet. Các lệnh cập
nhật thực sự được chứa trong các thuộc tính của DataAdapter. Chi tiết về DataAdapter
sẽ được đề cập ở phần sau.

     Để minh họa cách thức làm việc với DataAdapter và DataSet, đoạn code dưới
đây giới thiệu cách tạo ra một đối tượng DataTable, nạp dữ liệu từ một cơ sở dữ liệu,
và đưa nó vào một DataSet.

string sql = "SELECT MaSinhVien, HoTen, NgaySinh FROM SinhVien";

string connStr = "Data Source=MYSERVER;Initial Catalog=qlsinhvien;
           User Id=k28;Password=k28;";

// (1) Tao doi tuong data adapter
SqlDataAdapter da = new SqlDataAdapter(sql, connStr);

// (2) Tao doi tuong dataset
DataSet ds = new DataSet();

// (3) Tao mot Table co ten “SinhVien” trong dataset va nap du lieu cho no
da.Fill(ds, "SinhVien");

// (4) Hien thi danh sach ten sinh vien ra list box
DataTable dt = ds.Tables["SinhVien"];

for (int i=0; i< dt.Rows.Count;i++)
{
  DataRow row = dt.Rows[i];
  listBox1.Items.Add(row["HoTen"]);
}

     Bước đầu tiên là tạo ra một thể hiện của SqlDataAdapter bằng cách truyền một
câu lệnh SELECT và chuỗi kết nối cho phương thức khởi dựng của lớp này.
DataAdapter sẽ lo đến việc tạo ra đối tượng Connection cũng như việc mở, đóng
Connection khi cần thiết. Sau khi một DataSet rỗng sẽ được tạo ra, phương thức Fill()



                   Bài tập thực hành Chuyên đề Visual Studio .NET   77
của DataAdapter sẽ tạo ra một DataTable có tên là “SinhVien” trong DataSet và nạp
các dòng dữ liệu vào DataTable này (bằng câu lện SQL dạng SELECT của
DataAdapter). Mỗi column của DataTable sẽ tương ứng với một column trong bảng
của cơ sở dữ liệu nguồn. Dữ liệu trong bảng dữ liệu sau đó được đưa vào một ListBox
bằng cách duyệt qua danh sách các dòng của DataTable.

3.3 Làm việc với mô hình Kết nối trong ADO.NET
        Như đã mô tả tổng quan trong phần trước, mô hình Kết nối được dựa trên việc
thiết lập một Connection đến CSDL và sau đó sử dụng các Command để thực hiện
việc thêm, xóa, sửa, hay đọc dữ liệu từ data source (nguồn dữ liệu) được kết nối. Đặc
điểm phân biệt của mô hình này đó là các Command được phát sinh, làm việc với data
source thông qua một Connection đang hoạt động – Connection này sẽ mở cho đến khi
các thao tác được hoàn tất. Cho dù là làm việc với mô hình Kết nối hay Ngắt kết nối,
bước đầu tiên trong quá trình truy xuất một data source đó là tạo ra một đối tượng
Connection để làm đường truyền giữa ứng dụng với data source.

3.3.1 Lớp Connection
Có nhiều lớp Connection trong ADO.NET – mỗi lớp tương ứng với một Data Provider
– bao gồm SqlConnection, OracleConnection, OleDbConnection, OdbcConnection.
Mặc dù mỗi lớp có thể gồm những đặc tính riêng, nhưng các lớp này đều phải
implement interface IDbConnection. Bảng dưới đây tóm tắt các thành phần được định
nghĩa bởi interface này.

Loại     Tên               Mô tả
Property ConnectionString  Get/Sets chuỗi kết nối đến data source.
Property ConnectionTimeout Khoảng thời gian tối đa tính bằng giây để chờ thực
                           hiện việc kết nối đến data source
Property Database          Tên CSDL ứng với Connection hiện tại
Property State             Trạng thái hiện tại của Connection. Trả về một giá
                           trị kiểu liệt kê (enumeration): Broken, Closed,
                           Connecting, Executing, Fetching, hoặc Open
Method Open                Mở một Connection. Roll back mọi thao tác đang
         Close             làm dở.
                           Đóng Connection – trả Connection cho Connection
                           Pool nếu như có sử dụng Connection Pool
Method BeginTransaction    Khởi tạo một database transaction
Method ChangeDatabase      Thay đối CSDL hiện tại cho Connection đang mở.
                           Chuỗi mô tả tên CSDL mới được truyền cho phương
                           thức này
Method CreateCommand       Tạo ra một đối tượng Command ứng với Connection




                 Bài tập thực hành Chuyên đề Visual Studio .NET   78
3.3.1.1 Connection string
      Thuộc tính ConnectionString xác định data source và các thông tin cần thiết để
truy xuất data source, chẳng hạn như User ID và Password, … Ngoài những thông tin
cơ bản này, Connection string còn có thể chứa các giá trị cho các trường dữ liệu đặc
trưng cho data provider. Ví dụ, Connection string cho Ms Sql Server có thể chứa các
giá trị để quy định Connection Timeout và Packet Size.

     Dưới đây là các ví dụ về cách thành lập chuỗi kết nối cho các data provider
thường gặp. Danh sách đầy đủ về cách thành lập các chuỗi kết nối được cho ở Phụ lục
A.

     • SqlConnection sử dụng cơ chế xác thực kiểu SQL Server:

         “server=;database=;uid=;pwd=” hoặc

         “Data Source=;Initial Catalog=;User ID=;Password=”

     •   SqlConnection sử dụng cơ chế xác thực kiểu Windows:

         “Server=;Database=;Trusted_Connection=yes”

         Ở đây,  là tên/máy chủ chứa CSDL,  là tên CSDL,  là tên đăng nhập,
          là mật khẩu tương ứng.

         Ví dụ: “server=192.168.0.1;database=qlnhanvien;uid=k28;pwd=spider” hoặc
         “Server=192.168.0.1;Database=qlnhanvien;Trusted_Connection=yes”

     • OleDbConnection sử dụng để kết nối CSDL Access phiên bản trước 2003:

         “Provider=Microsoft.Jet.OLEDB.4.0;DataSource=”

         Ví dụ:

          o string stConnection =
             string.Format(“Provider=Microsoft.Jet.OLEDB.4.0;DataSource={0}”,
             @”c:program filesqlnhanvien.mdb”);

          o Sử dụng trong ứng dụng Internet: string stConnection =
             string.Format(“Provider=Microsoft.Jet.OLEDB.4.0;DataSource={0}”,
             Server.MapPath(“/data/qlnhanvien.mdb”);

     •   ODBC: “DSN=” với  là Data Source Name (DSN), ví dụ
         “DSN=qlnhanvien”

Các Connection string được dùng để tạo ra đối tượng Connection. Cách thực hiện
thông thường là truyền chuỗi này cho hàm khởi dựng như ví dụ dưới đây:



                  Bài tập thực hành Chuyên đề Visual Studio .NET   79
string stConnection =
        "Data Source=192.168.0.1;” +
        “Initial Catalog=films;” +
        “User Id=k28;”+
        “Password=spider";
SqlConnection cn = new SqlConnection(stConnection);
cn.Open(); //Open connection

3.3.1.2 Connection Pooling
    Tạo một Connection là một quá trình tốn nhiều thời gian – trong một số trường
hợp, việc này thậm chí còn tốn thời gian hơn việc thực thi các Command. Để loại bỏ
điều này, ADO.NET cung cấp một khái niệm gọi là connection pool. Connection pool
quản lý các Connection có trùng Connection string để tối ưu hóa số lần thiết lập, hợp
lệ hóa thông tin kết nối.

     Các quy tắc quy định connection pool cần biết:

      -   Cơ chế Connection pooling được kích hoạt theo mặc định. Cơ chế này được
          tắt bằng cách thêm vào Connection string “Pooling=false” đối với
          SqlConnection hoặc “OLE DB Services=-4” đối với OleDbConnection.

      -   Mỗi connection pool được ứng với một connection string duy nhất. Khi có
          một Connection được yêu cầu, pool handler (bộ quản lý pool) sẽ so sánh
          connection string với những connection string trong các pools đang tồn tại.
          Nếu có một Connection trùng khớp thì Connection tương ứng sẽ được xác
          định trong pool.

      -   Nếu tất cả các connection trong pool đang được sử dụng khi lại có yêu cầu về
          connection thì yêu cầu đó sẽ được xếp vào hàng đợi cho đến khi có một
          connection rảnh. Các connection sẽ được giải phóng khi phương thức Close
          hay Dispose của đối tượng connection được gọi.

      -   Connection pool được đóng khi tất cả các connection trong nó được giải
          phóng và hết thời gian (time out).

Đối với SQL Server, bạn có thể điều khiển hành vi của conneciton pooling bằng cách
gộp các cặp key-value vào connection string. Các key này có thể được sử dụng để thiết
lập số lượng nhỏ nhất và lớn nhất các connection trong pool cũng như xác định xem
một connection có cần phải reset khi nó được lấy từ pool ra hay không. Một key đặc
biệt chú ý đó là key có tên Lifetime, xác định thời gian mà connection có thể tồn tại
trước khi nó bị hủy bỏ. Giá trị này được kiểm tra khi một connection được trả về cho
pool. Nếu connection đã được mở trước đó, và lâu hơn giá trị Lifetime thì nó sẽ bị hủy.

     Đoạn code dưới đây minh họa các dùng các key này cho SqlClient:



                  Bài tập thực hành Chuyên đề Visual Studio .NET   80
string stConnection =
    "Server=192.168.0.1;” +
    “Trusted_Connection=yes;” +
    “database=qlnhanvien;" +
    "connection reset=false;" +
    "connection Lifetime=60;" + // Seconds
    "min pool size=1;" +
    "max pool size=50"; // Default=100

SqlConnection cn = new SqlConnection(cnString);

3.3.2 Đối tượng Command
Sau khi một đối tượng connection được tạo ra, bước tiếp theo trong quá trình truy xuất
CSDL – đối với mô hình Kết nối – đó là tạo ra một đối tượng Command để gửi một
query (select) hay một action command (thêm, xóa, sửa) đến data source. Có nhiều
loại lớp Command ứng với các data provider; các lớp này đều implement interface
IDbCommand.

3.3.2.1 Tạo đối tượng Command
Bạn có thể dùng một trong nhiều hàm khởi dựng để tạo đối tượng Command một cách
trực tiếp hoặc sử dụng cách tiếp cận ProviderFactory.

      Đoạn code dưới đây minh họa các tạo ra một đối tượng Command và thiết lập
các thuộc tính của nó.

SqlConnection conn = new SqlConnection(connstr);

conn.open();

string sql =
        "INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES (@pMaSinhVien,
@pHoTen)";

SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.commandText = sql;
cmd.Parameters.AddWithValue ("@pMaSinhVien", 12);
cmd.Parameters.AddWithValue ("@pHoTen", "tnv spider");

Trong trường hợp ứng dụng có thể phải sử dụng nhiều data provider, bạn nên sử dụng
cách tiếp cận provider factory. Factory được tạo ra bằng cách truyền chuỗi data
provider cho hàm khởi dựng của nó. Tiếp đến, phương thức CreateCommand được gọi
để trả về một đối tượng command.

string provider = "System.Data.SqlClient";


                 Bài tập thực hành Chuyên đề Visual Studio .NET   81
DBProviderFactory factory = DbProviderFactories.GetFactory(provider);

DbCommand cmd = factory.CreateCommand(); // DbCommand là một lớp trừu
tượng

cmd.CommandText = sql;       // sql là một chuỗi query hay command

cmd.Connection = conn;     // conn là một Connection

3.3.2.2 Thực thi một Command
Lệnh SQL được gán trong thuộc tính CommandText của đối tượng Command sẽ được
thực thi bằng một trong các phương thức được chỉ ra ở bảng dưới đây

Phương thức           Mô tả
ExecuteNonQuery       Thực thi truy vấn hành động (action query) và trả về số lượng
                      dòng dữ liệu bị ảnh hưởng bởi truy vấn đó:
                      cmd.CommandText = "DELETE SinhVien WHERE
                      MaSinhVien=12";

                      int soLuong = cmd.ExecuteNonQuery();

ExecuteReader         Thực thi một query và trả về đối tượng DataReader để có thể
                      truy cập tập kết quả của query đó. Phương thức này nhận một
                      tham số tùy chọn kiểu CommandBehavior để có thể tăng hiệu
                      năng thực thi query.
                      cmd.CommandText = "SELECT * FROM SinhVien” +
                       “WHERE YEAR(NgaySinh) > 1981”;

                      SqlDataReader rdr= cmd.ExecuteReader();

ExecuteScalar         Thực thi một query và trả về giá trị của cột đầu tiên trong dòng
                      đầu tiên của tập kết quả.
                      cmd.CommandText="SELECT COUNT(MaSinhVien) FROM
                      SinhVien";

                      int soSinhVien = (int)cmd.ExecuteScalar();

ExecuteXmlReader Chỉ có cho data provider SQL Server. Trả về một đối tượng
                 XmlReader dùng để truy xuất tập dữ liệu. Tham khảo thông tin
                 về XmlReader trong MSDN




                 Bài tập thực hành Chuyên đề Visual Studio .NET    82
ExecuteReader là phương thức quan trọng nhất trong các phương thức kể trên. Phương
thức này trả về một đối tượng DataReader giúp truy xuất đến các dòng dữ liệu trả về
bởi query. Xem ví dụ dưới đây:

dr = cmd.ExecuteReader(sql, );

Ở đây,  là một giá trị kiểu CommandBehavior để chỉ định behavior (hành vi) thực
thi của query. Một số data providers sử dụng  để tối ưu quá trình thực thi query.
Danh sách các giá trị và tác dụng của tham số  được mô tả chi tiết như dưới đây:

   •       SingleRow. Chỉ định rằng query chỉ trả về 1 dòng dữ liệu. Behavior mặc định là
           trả về nhiều tập kết quả.
   •       SingleResult. Query trả về một giá trị tuyến tính đơn nhất (single scalar value).
   •       KeyInfo. Trả về thông tin về column và primary key. Behavior này được sử
           dụng với phương thức GetSchema của DataReader để lấy thông tin về các
           column trong lược đồ (schema).
   •       SchemaOnly. Dùng để lấy về tên của các cột trong tập dữ liệu trả về:

           Ví dụ

           dr = cmd.ExecuteReader(CommandBehavior.SchemaOnly);

           string col1 = dr.GetName(0); // tên cột đầu tiên

   •       SequentialAccess. Cho phép dữ liệu trong dòng trả về có thể được truy xuất
           tuần tự theo column. Behavior này được dùng cho các trường dữ liệu BLOB
           hay TEXT.
   •       CloseConnection. Đóng connection khi DataReader được đóng.

3.3.2.3 Thực thi Stored Procedure (thủ tục lưu trữ sẵn) với đối tượng Command
Một stored procedure là một đoạn code SQL được lưu sẵn trong CSDL và có thể được
thực thi như là một script. ADO.NET hỗ trợ việc thực thi các stored procedure cho các
data provider OleDb , SqlClient, ODBC, và OracleClient.

       Các bước để thực thi một stored procedure:

       -     Thiết lập thuộc tính SqlCommand.CommandText thành tên của procedure;

       -     Thiết lập thuộc tính CommandType là CommandType.StoredProcedure;

       -     Thiết lập các Parameter (nếu có) cho procedure

       -     Thực thi phương thức ExecuteNonQuery.

Thủ tục dưới đây cho phép các mẫu tin lấy về từ bảng SinhVien được phân thành từng
nhóm các trang, mỗi trang 10 record. Đầu vào của của procedure là @pTrang (số hiệu


                      Bài tập thực hành Chuyên đề Visual Studio .NET   83
trang cần lấy); đầu ra của procedure là số trang tổng cộng của tập dữ liệu. Đoạn code
minh họa phía dưới thực hiện việc thiết lập để lấy về trang dữ liệu đầu tiên.

SqlCommand cmd = new SqlCommand();

cmd.CommandText = "spListSinhVien"; // tên procedure

cmd.CommandType = CommandType.StoredProcedure;

cmd.Parameters.Add(“@pTrang", SqlDbType.Int);

cmd.Parameters.Add(“@pTongSoTrang", SqlDbType.Int);

cmd.Parameters[0].Direction= ParameterDirection.Input;
cmd.Parameters[0].Value= 1;     // thiết lập để lấy về trang đầu tiên

cmd.Parameters[1].Direction=ParameterDirection.Output;

cmd.CommandTimeout=10; // Cho command tối đa 10s để thực thi

SqlDataReader dr = cmd.ExecuteReader();

while (dr.Read())
{
  // xử lý tập dữ liệu ở đây
}

dr.Close(); // DataReader phải được đóng trước khi đọc tham số đầu ra

int tongSoTrang = cmd.Parameters[1].Value;

Ví dụ này sử dụng data provider SqlClient. Có thể chỉnh sửa một phần nhỏ thì nó cũng
có thể hoạt động với OleDb. Điểm khác biệt mấu chốt giữa SqlClient và OleDb đó là
cách quản lý các parameter. SqlClient yêu cầu tên parameter phải đúng với tên
parameter của stored procedure; trong khi đó OleDb lại truyền các parameter cho
stored procedure dựa vào vị trí, vì vậy tên parameter là không quan trọng. Nếu
procedure trả về giá trị kết quả, OleDb phải thiết kế để parameter đầu tiên trong danh
sách làm nhiệm vụ này. Với SqlClient, chúng ta chỉ cần thêm một parameter với một
tên nào đó và xác định hướng trả về (direction) của parameter này là Return Value.

Phần code của stored procedure là như sau:

CREATE PROCEDURE spListSinhVien
 @pTrang int,
 @pTongSoTrang int output




                   Bài tập thực hành Chuyên đề Visual Studio .NET   84
AS

 /*
        Thủ tục trả về một tập kết quả gồm các SinhVien xếp theo HoTen.
        Tập kết quả được phân thành các trang, mỗi trang 10 SinhVien.
 */

 SET NOCOUNT ON

 SELECT @pSoTrang = CEILING(COUNT(*)/10) FROM SinhVien

 if @pTrang = 1 or @pTrang <1
 begin
    SELECT TOP MaSinhVien, HoTen FROM SinhVien ORDER BY HoTen
    set @pTrang = 1
    return 0
 end

 if @pTrang > @pTongSoTrang
 begin
    SET @pTrang = @pTongSoTrang
    declare @RowCount int

     set @RowCount = (@pTrang * 10)



     exec (
            'SELECT *
            FROM (
                 SELECT TOP 10 a.*
                 FROM (
                        SELECT TOP ' + @RowCount + ' *
                        FROM SinhVien
                        ORDER BY HoTen
                 )a
            ORDER BY HoTen desc
            )b
        ORDER BY HoTen'
        )

   return 0
 end



                  Bài tập thực hành Chuyên đề Visual Studio .NET   85
3.3.2.4 Sử dụng Parameter trong các Command không phải là Stored Procedures
Trong các query được thành lập trực tiếp (chứ không phải là stored procedure như ở
trên), chúng ta cũng có thể sử dụng các Parameter. Ví dụ dưới đây minh họa cách thức
bổ sung một record vào bảng SinhVien:

string sql =
        "INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES (@pMaSinhVien,
@pHoTen)";

SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.commandText = sql;
cmd.Parameters.AddWithValue("@pMaSinhVien", 12);
cmd.Parameters.AddWithValue("@pHoTen", "tnv spider");

Một cách khác để thực hiện việc bổ sung record như trên là sử dụng phép nối chuỗi4
như thế này:

int iMaSinhVien = 12;
string stHoTen = "tnv spider";

sql = string.Format(“INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES ({0},
‘{1}’)”,
        iMaSinhVien, stHoTen);

SqlCommand cmd = new SqlCommand(sql, conn);

3.3.3 Đối tượng DataReader
Như đã thấy trong các ví dụ trước, một DataReader cho phép lấy các dòng và cột dữ
liệu của dữ liệu trả về khi thực thi một query. Việc truy xuất dòng được định nghĩa bởi
interface IDataRecord. Dưới đây là các member quan trọng của interface này.

3.3.3.1 Truy xuất các dòng dữ liệu với DataReader
DataReader lấy về từng dòng đơn (single row) từ một tập dữ liệu trả về mỗi khi
phương thức Read của nó được thực thi. Nếu không có dòng dữ liệu nào thì phương
thức này trả về giá trị false. DataReader phải được đóng sau khi các thao tác xử lý các
dòng được hoàn tất để giải phóng tài nguyên hệ thống. Bạn có thể sử dụng thuộc tính
DataReader.IsClosed để biết được DataReader đã được đóng hay chưa.



4
  Trong thực tế, giải pháp nối chuỗi ít khi được sử dụng vì lý do an toàn dữ liệu. Hãy hình dung trong đoạn code
này, nếu stHoTen được gán giá trị là “tnv spider’); DELETE * FROM SinhVien;--”. Khi đó query được thực thi sẽ
là “INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES (12, ‘tnv spider’); DELETE * FROM SinhVien;--)”
Lỗ hổng kiểu này thường được gọi với tên SQL Injection.




                       Bài tập thực hành Chuyên đề Visual Studio .NET                86
Mặc dù DataReader là ứng với một Command đơn, nhưng Command này lại có
thể chứa nhiều query trong đó, do đó có thể trả về nhiều tập dữ liệu kết quả. Đoạn code
dưới đây minh họa cách xử lý các dòng dữ liệu trả về bởi 2 query trong một
Command.

string q1 = "SELECT * FROM SinhVien WHERE YEAR(NgaySinh) < 1981";

string q2 = "SELECT * FROM SinhVien WHERE YEAR(NgaySinh) > 1990";

cmd.CommandText = q1 + ";" + q2; // hai query được ngăn cách nhau bởi dấu ;

DbDataReader rdr = cmd.ExecuteReader();

bool readNext = true;

while (readNext)
{
  while (rdr.Read())
   MessageBox.Show(rdr.GetString(1));

    readNext = rdr.NextResult(); // kiem tra xem con tap du lieu nao nua khong
}

rdr.Close();

conn.Close();

      DataReader không có thuộc tính hay phương thức nào cho biết số lượng dòng dữ
liệu trả về trong tập dữ liệu của nó (do đặc tính forward-only của DataReader), tuy
nhiên, chúng ta có thể sử dụng thuộc tính HasRows (kiểu Boolean) của DataReader để
xác định xem nó có 1 hay nhiều dòng để đọc hay không.

3.3.3.2 Truy xuất giá trị của column
Có nhiều cách để truy xuất dữ liệu chứa trong các columns của dòng hiện tại của
DataReader:

     -   Truy xuất như là một array dùng số thứ tự column (bắt đầu từ 0) hoặc dùng tên
         column
     -   Sử dụng phương thức GetValue bằng cách truyền cho phương thức này số thứ
         tự của column
     -   Sử dụng một trong các phương thức định kiểu GetXXX, bao gồm GetString,
         GetInt32, GetDateTime, GetDouble, …
Đoạn code dưới đây minh họa các thức truy xuất giá trị dữ liệu của các column.




                    Bài tập thực hành Chuyên đề Visual Studio .NET   87
cmd.CommandText =
     "SELECT MaSinhVien, Hoten, GioiTinh, NgaySinh FROM SinhVien ” +
     “WHERE YEAR(NgaySinh) = 1981";

dr = cmd.ExecuteReader();

dr.Read();

// Các cách để lấy dữ liệu kiểu string ở cột thứ 2 (HoTen)
string stHoTen;

stHoTen = dr.GetString(1);

stHoTen = (string)dr.GetSqlString(1); // SqlClient provider

stHoTen = (string)dr.GetValue(1);

stHoTen = (string)dr["HoTen"];

stHoTen = (string)dr[1];

// Lấy dữ liệu kiểu DateTime ở cột thứ 4 (NgaySinh) có kiểm tra giá trị NULL
if (!dr.IsDbNull(3))
        DateTime dtNgaySinh = dr.GetDateTime(3);

     Phương thức GetString có điểm thuận lợi trong việc ánh xạ nội dung dữ liệu từ
CSDL sang kiểu dữ liệu của .NET. Các cách tiếp cận khác đều trả về các kiểu đối
tượng có yêu cầu phép chuyển kiểu. Vì lý do này, bạn nên sử dụng các phương thức
GetXXX cho các kiểu dữ liệu xác định. Cũng lưu ý rằng, phương thức GetString
không yêu cầu phép chuyển kiểu, nhưng bản thân nó không thực hiện bất cứ phép
chuyển đổi nào; chính vì thế, nếu dữ liệu là không đúng như kiểu dữ liệu trông đợi sẽ
có Exception được trả ra.

      Nhiều ứng dụng phụ thuộc vào tầng xử lý dữ liệu để cung cấp DataReader. Với
những trường hợp như thế, ứng dụng có thể sử dụng metadata (siêu dữ liệu) để xác
định tên column, kiểu dữ liệu của column, và các thông tin khác về column. Đoạn
code sau đây minh họa việc in ra danh sách các tên và kiểu dữ liệu của các column mà
đối tượng DataReader đang quản lý:

// In ra danh sách các tên column của một đối tượng DataReader có tên dr

for (int i = 0; i < dr.FieldCount; i++)
  Console.WriteLine(“Column {0} co kieu du lieu {1}”,
                        dr.GetName(i), dr.GetDataTypeName(i)); // Column name




                  Bài tập thực hành Chuyên đề Visual Studio .NET   88
Có một cách khác toàn diện hơn để quản lý toàn bộ thông tin về lược đồ (schema) của
tập dữ liệu kết quả trả về, đó là sử dụng phương thức GetSchemaTable. Phương thức
này trả về một đối tượng DataTable mà mỗi dòng trong DataTable này sẽ biểu diễn
một column trong tập dữ liệu kết quả. Đoạn code dưới đây minh họa cách truy xuất tất
cả các thông tin về các column của một tập dữ liệu trả về.

DataTable schemaTable = dr.GetSchemaTable();

int stt = 0;

foreach (DataRow r in schemaTable.Rows)
{
  foreach (DataColumn c in schemaTable.Columns)
  {
    Console.WriteLine(stt.ToString() + " " + c.ColumnName + ": " + r[c]);
    stt++;
  }
}

Kết quả hiển thị:
    0 ColumnName: movie_ID
    1 ColumnOrdinal: 0
    … //không liệt kê
    12 DataType: System.Int32
    … //không liệt kê

3.3.4 Ví dụ
Giả sử ta đã có cơ sở dữ liệu quanlythuvien trong SQL Server có quan hệ như sau:




                 Bài tập thực hành Chuyên đề Visual Studio .NET   89
Ví dụ về đối tượng Connection, Command và DataReader

     Thiết kế Form để tạo mới 1 tài khoản như sau (làm việc trên bảng nhanvien):




                                        ListView1




Frmtaomoitk sử dụng các trường, phương thức và sự kiện sau:




     Các điều khiển

Tên điều khiển                                  Thuộc tính



                 Bài tập thực hành Chuyên đề Visual Studio .NET   90
Form             Name: Frmtaomoitk
                 Text:Tạo mới một tài khoản sử dụng chương trình
listView         Name:listView1
                 Columns: Add thêm 4 cột: Họ tên, Địa chỉ, Tên đăng nhập và
                 Quyền hạn
                 View: Details
                 GridLines:True
groupBox         Name:groupBox1
                 Text: Thông tin cơ bản
Label            Tạo ra 5 label để hiển thị: Mã nhân viên, Họ tên, Địa chỉ, Tên đăng
                 nhập và quyền hạn.
TextBox          Tạo ra 4 TextBox lần lượt với các tên: txtmanv, txthoten, txtdiachi,
                 txttendangnhap
Button           Tạo 8 button lần lượt với các tên butdau, butlui, buttien, butcuoi,
                 buttaomoi, buttimkiem, butxoabo,butthoat
Các trường:

    Tên trường                                    Ý nghĩa
Cn               Dùng để kết nối đến cơ sở dữ liệu quanlythuvien
cmdSelect        sqlCommand sử dụng câu lệnh select để hiển thị và tìm kiếm
cmdInsert        sqlCommand sử dụng câu lệnh Insert để tạo thêm 1 tài khoản
cmdXoa           sqlCommand sử dụng câu lệnh Delete để xóa 1 tài khoản
I                Tài khoản thứ i
Các phương thức
+ Hàm dựng Frmtaomoitk để tạo giao diện
public Frmtaomoitk()
     { InitializeComponent(); }
+ Phương thức Moketnoi(): Kiểm tra đường kết nối, nếu đang mở thì đóng lại, sau đó
mở lại đường kết nối

private void Moketnoi()
     {
        if (cn.State == ConnectionState.Open)
           cn.Close();
        cn.Open();
      }

+ Phương thức LoadListView: Lấy dữ liệu của bảng nhanvien nạp dữ liệu lên
listView1. Phương thức này được gọi khi thay đổi dữ liệu trong bảng nhận viên như
nhâp thêm hoặc xóa đi 1 nhân viên. Sử dụng 2 đối tượng SqlCommand,
SqlDataReader
 private void LoadListView()


                 Bài tập thực hành Chuyên đề Visual Studio .NET   91
{
        Moketnoi();
        cmdSelect = new SqlCommand("select * from nhanvien", cn);
        SqlDataReader r = cmdSelect.ExecuteReader();
        listView1.Items.Clear(); // Xóa tất cả dữ liệu trong listView1
        while (r.Read())
        {
           string[] st = new string[5];
           st[0] = r[0].ToString();
           st[1] = r[1].ToString();
           st[2] = r[2].ToString();// Không hiển thị mật khẩu, nên không có r[3]
           st[3] = r[4].ToString();
           st[4] = r[5].ToString();
           ListViewItem lv = new ListViewItem(st);
           listView1.Items.Add(lv);
        }
        cmdSelect.Dispose();
     }
+ Phương thức LoadItem: Lấy dữ liệu từ dòng thứ i của listView1 đưa vào txtmanv,
txthoten, txtdiachi, txttendangnhap và comboBox1. Phương thức này được gọi khi di
chuyển qua lại thông tin của các nhân viên.

 private void LoadItem(int i)
     {
       txtmanv.Text = listView1.Items[i].Text;
       txthoten.Text = listView1.Items[i].SubItems[1].Text;
       txtdiachi.Text = listView1.Items[i].SubItems[2].Text;
       txttendangnhap.Text = listView1.Items[i].SubItems[3].Text;
       comboBox1.Text = listView1.Items[i].SubItems[4].Text;
     }
+ Phương thức LoadCombox: Đưa dữ liệu vào cho comboBox1. Giả sử chỉ có 3
quyền hạn: admin, sinhvien và Thuthu. Phương thức này được gọi khi vừa nạp Form
lên

private void LoadCombox()
     {
        comboBox1.Items.Add("Admin");
        comboBox1.Items.Add("Sinhvien");
        comboBox1.Items.Add("ThuThu");
        comboBox1.Text = "Admin";
}




                  Bài tập thực hành Chuyên đề Visual Studio .NET   92
+ Phương thức XoaTextBox: Xóa hết dữ liệu trong các textBox, phương thức này
được goi khi nhập thêm 1 tài khoản.

 private void XoaTextBox()
     {
       txtmanv.Clear();
       txthoten.Clear();
       txtdiachi.Clear();
       txttendangnhap.Clear();
       txtmanv.Focus();
     }
+ Phương thức KiemTraMa: Kiểm tra xem có mã nhân viên nào bằng với ma hay
không. Phương thức này được gọi khi nhập thêm 1 tài khoản

 private int KiemTraMa(string ma)
     {
       Moketnoi();
       cmdSelect = new SqlCommand("select count(*) from nhanvien
                                 where manhanvien='"+ma.Trim()+"'");
       cmdSelect.Connection = cn;
       return (int)cmdSelect.ExecuteScalar();
     }
+ Sự kiện Frmtaomoitk_Load: Tạo và mở ra đường kết nối đến cơ sở dữ liệu
quanlythuvien, tên máy chủ nhha, sử dụng cơ chế xác thực kiểu Windows, nạo dữ liệu
vào cho các điều khiển.

private void Frmtaomoitk_Load(object sender, EventArgs e)
     { try
        {
           cn = new SqlConnection("Data Source=nhha;Initial Catalog=quanlythuvien;
                                                 Trusted_Connection=yes");
           cn.Open();
        }
        catch (Exception loi) { MessageBox.Show("Không thể kết nối được"); }
        LoadListView(); //Nạp dữ liệu vào cho listView1
        i = 0;
        LoadItem(i);// Nạp dữ liệu vào cho các textBox và comboBox1
        LoadCombox();
     }
+ Sự kiện butdau_Click: Nạp dữ liệu của dòng đầu tiên từ listView1 vào cho các
textBox và comboBox

private void butdau_Click(object sender, EventArgs e)


                 Bài tập thực hành Chuyên đề Visual Studio .NET   93
{
         i = 0;
         LoadItem(i);
     }
+ Sự kiện buttien_Click: Nạp dữ liệu của dòng tiếp theo từ listView1 vào cho các
textBox và comboBox

private void buttien_Click(object sender, EventArgs e)
     {
        i++;
        if (i == listView1.Items.Count) i = listView1.Items.Count - 1;
        LoadItem(i);
     }
+ Sự kiện butlui_Click:

private void butlui_Click(object sender, EventArgs e)
    {
       i--;
       if (i < 0) i = 0;
       LoadItem(i);
    }
+ Sự kiện butcuoi_Click:

 private void butcuoi_Click(object sender, EventArgs e)
   {
     i = listView1.Items.Count - 1;
     LoadItem(i);
   }
+ Sự kiện butTaomoi_Click: Được sử dụng để thêm 1 tài khoản (1 nhân viên), nút
butTaomoi có 2 trạng thái tạo mới và lưu. Nếu người sử dụng kích vào nút Tạo mới sẽ
chuyển sang trạng thái là lưu và ngược lại.

  private void butTaomoi_Click(object sender, EventArgs e)
     {
        if (butTaomoi.Text.Equals("Tạo mới"))
        {
           XoaTextBox();
           butTaomoi.Text = "Luu";
        }
        else
       // Kiểm tra xem mã nhân viên này có hay chưa ?
        if (KiemTraMa(txtmanv.Text)==1)          {
           MessageBox.Show("Mã này đã có");


                   Bài tập thực hành Chuyên đề Visual Studio .NET   94
txtmanv.Clear();
         txtmanv.Focus();
       }
       else
       {
          string ma = txtmanv.Text;
          string hoten = txthoten.Text;
          string diachi = txtdiachi.Text;
          string tendangnhap = txttendangnhap.Text;
          string matkhau = "";// Khi tạo 1 tài khoản thì mật khẩu ban đầu là rỗng
          string quyenhan = comboBox1.Text;
          Moketnoi();
          string sql="insert into nhanvien values("+"'"+ma+"','"+hoten+"','"+diachi
                            +"','" +tendangnhap+"','"+matkhau+"','"+quyenhan +"')";
          cmdInsert = new SqlCommand(sql,cn);
          cmdInsert.ExecuteNonQuery();
          MessageBox.Show("Đã lưu thành công");
          LoadListView(); //Nạp lại dữ liệu mới vào listView1
          butTaomoi.Text = "Tạo mới";
          cmdInsert.Dispose();
       }
     }
+ Sự kiện buttimkiem_Click: Khi người sử dụng gõ 1 mã nhân viên vào txtmanv và
kích vào nút buttimkiem, nếu tìm thấy mã nhân viên này sẽ hiển thị kết quả lên các
textBox và comboBox

private void buttimkiem_Click(object sender, EventArgs e)
    {
       Moketnoi();
       string sql = "select * from nhanvien where manhanvien='" + txtmanv.Text + "'";
       cmdSelect = new SqlCommand(sql,cn);
       SqlDataReader dr = cmdSelect.ExecuteReader();
       if (dr.Read())// Đã tìm thấy
       {
          txtmanv.Text = dr[0].ToString();
          txthoten.Text = dr[1].ToString();
          txtdiachi.Text = dr[2].ToString();
          txttendangnhap.Text = dr[4].ToString();
          comboBox1.Text = dr[5].ToString();
       }
       else
          MessageBox.Show("Không tìm thấy");


                 Bài tập thực hành Chuyên đề Visual Studio .NET   95
}
+ Sự kiện butXoabo_Click: Xóa nhân viên có mã nhân viên ở txtmanv

private void butXoabo_Click(object sender, EventArgs e)
     {
        DialogResult dr = MessageBox.Show("Chắc chắn xóa hay không ?",
                                           "Thông báo", MessageBoxButtons.YesNo);
        if (dr == DialogResult.Yes) // Nếu người sử dụng chọn nút yes
        {
           Moketnoi();
           string Sql = "delete from nhanvien where manhanvien='" + txtmanv.Text + "'";
           cmdXoa = new SqlCommand(Sql,cn);
           if (cmdXoa.ExecuteNonQuery() == 1)
           {
               MessageBox.Show("Xóa thành công");
               LoadListView();
               LoadItem(0);
           }
           else
               MessageBox.Show("Không tồn tại mã nhân viên " + txtmanv.Text);
           cmdXoa.Dispose();
        }
     }

3.5 Làm việc với mô hình Ngắt kết nối: DataSet và DataTable
Mô hình Ngắt Kết nối của ADO.NET dựa trên cơ sở sử dụng đối tượng DataSet như là
một vùng nhớ đệm. Một đối tượng DataAdapter làm nhiệm vụ trung gian giữa DataSet
và data source (nguồn dữ liệu) để nạp dữ liệu vào vùng nhớ đệm. Sau khi DataAdapter
hoàn thành nhiệm vụ nạp dữ liệu, nó sẽ trả đối tượng Connection về pool, vì thế nó
ngắt kết nối khỏi nguồn dữ liệu.

3.4.1 Lớp DataSet
DataSet đóng vai trò của một CSDL in-memory (CSDL nằm trong bộ nhớ). Thuộc
tính Tables của DataSet là một tập hợp các DataTable chứa dữ liệu và lược đồ dữ liệu
(data schema) mô tả dữ liệu trong DataTable. Thuộc tính Relations chứa tập hợp các
đối tượng DataRelation xác định cách thức liên kết các đối tượng DataTable của
DataSet. Lớp DataSet cũng hỗ trợ việc sao chép, trộn, và xóa DataSet thông qua các
phương thức tương ứng là Copy, Merge, và Clear.

    DataSet và DataTable là phần lõi của ADO.NET và chúng không là đặc trưng
của một data provider nào (giống như ở các lớp Connection, DataReader,
DataAdapter). Một ứng dụng có thể định nghĩa và nạp dữ liệu từ nguồn bất kỳ (chứ
không nhất thiết là từ một CSDL) vào DataSet.


                  Bài tập thực hành Chuyên đề Visual Studio .NET   96
Bên cạnh các DataTable và các DataRelation, một DataSet còn có thể chứa các
thông tin tùy biến khác được định nghĩa bởi ứng dụng. Hình dưới đây mô tả cả lớp
chính trong DataSet. Trong số các thuộc tính này, chú ý thuộc tính PropertyCollection;
đó là các thuộc tính được lưu trữ dưới dạng một hash table (bảng băm), thường chứa
một giá trị time stamp hay các thông tin đặc tả như các yêu cầu hợp lệ hóa (validation
requirements) cho column trong các DataTable trong DataSet.




3.4.1.1 DataTable
Thuộc tính DataSet.Tables chứa các đối tượng DataTable. Mỗi đối tượng trong tập
hợp này có thể được truy xuất bằng chỉ số hoặc bằng tên.

     Các DataTable trong tập hợp DataSet.DataTables mô phỏng các Table trong
CSDL quan hệ (các row, column, …). Các thuộc tính quan trọng nhất của lớp
DataTable là Columns và Rows định nghĩa cấu trúc và nội dung bảng dữ liệu.

3.4.1.2 DataColumn
Thuộc tính DataTable.Columns chứa một tập các đối tượng DataColumn biểu diễn các
trường dữ liệu trong DataTable. Bảng dưới đây tóm tắt các thuộc tính quan trọng của
lớp DataColumn.


Phương thức      Mô tả

ColumnName       Tên column




                 Bài tập thực hành Chuyên đề Visual Studio .NET   97
Phương thức     Mô tả

DataType        Kiểu của dữ liệu chứa trong column này
                Ví dụ: col1.DataType = System.Type.GetType("System.String")

MaxLength       Độ dài tối đa của một text column. -1 nếu không xác định độ dài tối
                đa

ReadOnly        Cho biết giá trị của column có được chỉnh sửa hay không

AllowDBNull     Giá trị Boolean cho biết column này có được chứa giá trị NULL hay
                không

Unique          Giá trị Boolean cho biết column này có được chứa các giá trị trùng
                nhau hay không

Expression      Biểu thức định nghĩa cách tính giá trị của một column
                Ví dụ: colTax.Expression = "colSales * .085";

Caption         Tiêu đề hiển thị trong thành phần điều khiển giao diện đồ họa

DataTable       Tên của đối tượng DataTable chứa column này


Các column của DataTable được tạo ra một cách tự động khi table được nạp dữ liệu từ
kết quả của một database query hoặc từ kết quả đọc được ở một file XML. Tuy nhiên,
chúng ta cũng có thể viết code để tạo động các column. Đoạn code dưới đây sẽ tạo ra
một đối tượng DataTable, sau đó tạo thêm các đối tượng DataColumn, gán giá trị cho
các thuộc tính của column, và bổ sung các DataColumn này vào DataTable.

DataTable tb = new DataTable("DonHang");

DataColumn dCol = new DataColumn("MaSo", Type.GetType("System.Int16"));
dCol.Unique = true; // Dữ liệu của các dòng ở column này không được trùng nhau
dCol.AllowDBNull = false;
tb.Columns.Add(dCol);

dCol = new DataColumn("DonGia", Type.GetType("System.Decimal"));
tb.Columns.Add(dCol);

dCol = new DataColumn("SoLuong",Type.GetType("System.Int16"));
tb.Columns.Add(dCol);




                 Bài tập thực hành Chuyên đề Visual Studio .NET   98
dCol= new DataColumn("ThanhTien",Type.GetType("System.Decimal"));
dCol.Expression= "SoLuong*DonGia";
tb.Columns.Add(dCol);

// Liệt kê danh sách các Column trong DataTable
foreach (DataColumn dc in tb.Columns)
{
   Console.WriteLine(dc.ColumnName);
   Console.WriteLine(dc.DataType.ToString());
}

Để ý rằng column MaSo được định nghĩa để chứa các giá trị duy nhất. Ràng buộc này
giúp cho column này có thể được dùng như là trường khóa để thiết lập relationship
kiểu parent-child với một bảng khác trong DataSet. Để mô tả, khóa phải là duy nhất –
như trong trường hợp này – hoặc được định nghĩa như là một primary key của bảng.
Ví dụ dưới đây mô tả cách xác định primary key của bảng:

DataColumn[] col = {tb.Columns["MaSo"]};
tb.PrimaryKey = col;

Nếu một primary key chứa nhiều hơn 1 column – chẳng hạn như HoDem và Ten – bạn
có thể tạo ra một ràng buộc unique constraint trên các như ví dụ dưới đây:

DataColumn[] cols = {tb.Columns["HoDem"], tb.Columns["Ten"]};

tb.Constraints.Add(new UniqueConstraint("keyHoVaTen", cols));

Chúng ta sẽ xem xét cách thức tạo relationship cho các bảng và trộn dữ liệu ở phần
tiếp theo.

3.4.1.3 DataRows
Dữ liệu được đưa vào table bằng cách tạo mới một đối tượng DataRow, gán giá trị cho
các column của nó, sau đó bổ sung đối tượng DataRow này vào tập hợp Rows gồm các
DataRow của table.

DataRow row;
row = tb.NewRow();     // Tạo mới DataRow
row["DonGia"] = 22.95;
row["SoLuong"] = 2;
row["MaSo"] = 12001;

tb.Rows.Add(row);      // Bổ sung row vào tập Rows

Console.WriteLine(tb.Rows[0]["ThanhTien"].ToString()); // 45.90



                 Bài tập thực hành Chuyên đề Visual Studio .NET   99
Một DataTable có các phương thức cho phép nó có thể commit hay roll back các thay
đổi được tạo ra đối với table tương ứng. Để thực hiện được điều này, nó phải nắm giữ
trạng thái của mỗi dòng dữ liệu bằng thuộc tính DataRow.RowState. Thuộc tính này
được thiết lập bằng một trong 5 giá trị kiểu enumeration DataRowState sau: Added,
Deleted, Detached, Modifed, hoặc Unchanged. Xem xét ví dụ sau:

tb.Rows.Add(row);               // Added

tb.AcceptChanges();             // ...Commit changes

Console.Write(row.RowState);          // Unchanged

tb.Rows[0].Delete();            // Deleted

// Undo deletion
tb.RejectChanges();            // ...Roll back

Console.Write(tb.Rows[0].RowState); // Unchanged

DataRow myRow;

MyRow = tb.NewRow();                 // Detached

Hai phương thức AcceptChanges và RejectChanges của DataTable là tương đương với
các thao tác commit và rollback trong một CSDL. Các phương thức này sẽ cập nhất
mọi thay đổi xảy ra kể từ khi table được nạp, hoặc từ khi phương thức AcceptChanges
được triệu gọi trước đó. Ở ví dụ trên, chúng ta có thể khôi phục lại dòng bị xóa do thao
tác xóa là chưa được commit trước khi phương thức RejectChanges được gọi. Điều
đáng lưu ý nhất đó là, những thay đổi được thực hiện là ở trên table chứ không phải là
ở data source.

      ADO.NET quản lý 2 giá trị - ứng với 2 phiên bản hiện tại và nguyên gốc - cho
mỗi column trong một dòng dữ liệu. Khi phương thức RejectChanges được gọi, các
giá trị hiện tại sẽ được đặt khôi phục lại từ giá trị nguyên gốc. Điều ngược lại được
thực hiện khi gọi phương thức AcceptChanges. Hai tập giá trị này có thể được truy
xuất đồng thời thông qua các giá trị liệt kê DataRowVersion là: Current và Original:

DataRow r = tb.Rows[0];

r["DonGia"]= 14.95;
r.AcceptChanges();

r["DonGia"]= 16.95;




                 Bài tập thực hành Chuyên đề Visual Studio .NET   100
Console.WriteLine("Current: {0} Original: {1} ",
   r["Price", DataRowVersion.Current],
   r["Price", DataRowVersion.Original]);

Kết quả in ra:

Current: 16.95 Original: 14.95

3.4.1.4 DataView.
DataView đóng vai trò như tầng hiển thị dữ liệu lưu trữ trong DataTable. Nó cho phép
người sử dụng sắp xếp, lọc và tìm kiếm dữ liệu.

//Giả sử đã có 1 dataset có tên là ds chứa dữ liệu của bảng DonHang

DataView dv = new DataView(ds.Tables["DonHang”];

// Lọc ra tất cả các hàng có giá từ 10 đến 100

dv.RowFilter = "soluong>=10 and soluong<=100";

//Sắp xếp tăng dần theo số lượng nếu số lượng bằng nhau thì sắp xếp giảm dần thêm
đơn giá

dv.Sort = "soluong, dongia DESC";



3.4.2 Nạp dữ liệu vào DataSet
Chúng ta đã biết cách thành lập một DataTable và xử lý dữ liệu theo kiểu từng dòng
một. Phần này sẽ trình bày phương pháp để dữ liệu và lược đồ dữ liệu được nạp tự
động từ CSDL quan hệ vào các table trong DataSet.

3.4.2.1 Dùng DataReader để nạp dữ liệu vào DataSet
Đối tượng DataReader có thể được sử dụng để liên hợp đối tượng DataSet hay
DataTable trong việc nạp các dòng dữ liệu kết quả (của query trong DataReader).

cmd.CommandText = "SELECT * FROM nhanvien";

DBDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);

DataTable dt = new DataTable("nhanvien");

dt.Load(rdr); // Nạp dữ liệu và lược đồ vào table

Console.WriteLine(rdr.IsClosed); // True




                  Bài tập thực hành Chuyên đề Visual Studio .NET   101
Đối tượng DataReader được tự động đóng sau khi tất cả các dòng dữ liệu được nạp
vào table. Do đã sử dụng tham số CommandBehavior.CloseConnection trong phương
thức ExecuteReader nên connection được đóng sau khi DataReader được đóng.

       Nếu table đã có dữ liệu, phương thức Load sẽ trộn dữ liệu mới với các dòng dữ
liệu đang có trong nó. Việc trộn này xảy ra chỉ khi các dòng dữ liệu có chung primary
key. Nếu không có primary key được định nghĩa, các dòng dữ liệu sẽ được nối vào sau
tập dữ liệu hiện tại. Chúng ta có thể sử dụng phương thức nạp chồng khác của phương
thức Load để quy định cách thức làm việc. Phương thức Load với tham số kiểu
enumeration LoadOption gồm 1 trong 3 giá trị OverwriteRow, PreserveCurrentValues,
hoặc UpdateCurrentValues tương ứng với tùy chọn ghi đè nguyên dòng, giữ lại các giá
trị hiện tại, hoặc cập nhật các giá trị hiện tại. Đoạn code dưới đây minh họa cách trộn
dữ liệu vào các dòng hiện tại theo kiểu ghi đè các giá trị hiện tại:

cmd.CommandText = "SELECT * FROM nhanvien WHERE diachi=’a’";

DBDataReader rdr = cmd.ExecuteReader();
DataTable dt = new DataTable("nhanvien");

dt.Load(rdr);

Console.Write(dt.Rows[0]["HoTen"]); // giả sử giá trị nhận được là “tnv spider”

// Gán khóa chính

DataColumn[] col = new DataColumn[1];
col[0] = dt.Columns["Manv"];

dt.PrimaryKey = col;

DataRow r = dt.Rows[0]; // lấy dòng đầu tiên

r["HoTen"] = "ten moi"; // thay đổi giá trị của cột HoTen

// Do reader đã bị đóng sau khi nạp vào data table nên phải giờ phải fill lại
rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);

// Trộn dữ liệu với các dòng hiện tại. Ghi đè các giá trị hiện tại
dt.Load(rdr, LoadOption.UpdateCurrentValues);

// Giá trị cập nhật đã bị ghi đè!!!
Console.Write(dt.Rows[0]["HoTen"]); // “tnv spider”

3.4.2.2 Nạp dữ liệu vào DataSet bằng DataAdapter
Đối tượng DataAdapter có thể được dùng để nạp một table hiện có vào một table khác,
hoặc tạo mới và nạp dữ liệu cho table từ kết quả của một query. Bước đầu tiên là tạo ra


                  Bài tập thực hành Chuyên đề Visual Studio .NET     102
một đối tượng DataAdapter tương ứng với data provider cụ thể. Dưới đây là các ví dụ
để tạo ra đối tượng DataAdapter:

   (1) Tạo từ Connection string và câu truy vấn SELECT:

      String sql = "SELECT * FROM nhanvien";
      SqlDataAdapter da = new SqlDataAdapter(sql, connStr);

   (2) Tạo từ đối tượng Connection và câu truy vấn SELECT:

      SqlConnection conn = new SqlConnection(connStr);
      SqlDataAdapter da = new SqlDataAdapter(sql, conn);

   (3) Gán đối tượng Command cho thuộc tính SelectCommand

      SqlDataAdapter da = new SqlDataAdapter();
      SqlConnection conn = new SqlConnection(connStr);
      da.SelectCommand = new SqlCommand(sql, conn);

Sau khi đối tượng DataAdapter đã được tạo ra, phương thức Fill của nó được thực thi
để nạp dữ liệu vào table (đang tồn tại hoặc tạo mới). Ở ví dụ dưới đây, một table mới
được tạo ra với tên mặc định là “Table”:

DataSet ds = new DataSet();

// Tạo ra một DataTable, nạp dữ liệu vào DataTable, và đưa DataTable vào DataSet
int nRecs = da.Fill(ds); // trả về số lượng record được nạp vào DataTable

// Nếu muốn đặt tên cho DataTable trong DataSet thay vì lấy tên mặc định
// thì sử dụng code như thế này
int nRecs = da.Fill(ds, "nhanvien ")

Với một table đang tồn tại, tác dụng của lệnh Fill tùy thuộc vào table có primary hay
không. Nếu có, những dòng dữ liệu có khóa trùng với dòng dữ liệu mới sẽ được thay
thế. Các dòng dữ liệu mới không trùng với dữ liệu hiện có sẽ được nối vào sau
DataTable.

3.4.3 Ví dụ
Ví dụ về DataAdapter và DataSet

Ví dụ này sử dụng cơ sở dữ liệu quanlythuvien như trong ví dụ 3.3.4

Thiết kế form frmtimkiemsach để tìm theo tên sách hoặc tên tác giả như sau:




                 Bài tập thực hành Chuyên đề Visual Studio .NET   103
frmtimkiemsach sử dụng các trường, phương thức và sự kiện sau:




      Các điều khiển

Tên điều khiển                            Thuộc tính
Form           Name: frmtimkiemsach
               Text:Tìm kiếm theo nhan đề hoặc tên tác giả
Label          Text: Nhập tên sách hoặc tên tác giả cần tìm
TextBox        Name: txttimkiem
dataGridView   Name: dataGridView1
statusStrip    Name: thanhtrangthai
               Items: Add thêm 2 statusLabel: với tên ketquatim và tóngoluong
Các trường:

 Tên trường                                       Ý nghĩa
Cn               Dùng để kết nối đến cơ sở dữ liệu quanlythuvien
cmd              sqlCommand sử dụng câu lệnh select để hiển thị và tìm kiếm sách
da               SqlDataAdapter chứa cmd và cn



                Bài tập thực hành Chuyên đề Visual Studio .NET   104
ds                DataSet chứa dữ liệu của bảng sách hoặc chứa kết quả tìm kiếm
Các phương thức
+ Hàm dựng frmtimkiemsach để tạo giao diện
public frmtimkiemsach()
     {
        InitializeComponent();
     }

+ Phương thức Tongsoluong: được sử dụng để tính tổng số lượng sách của các sách
lưu trong Dataset ds.
  private int Tongsoluong()
     { int s=0;
         foreach (DataRow r in ds.Tables["sach"].Rows)
         {
            s += (int)r["soluong"];
         }
         return s;
     }
+ Phương thức Tongsoluongtk tính tổng số lượng sách trong DataView dv, dv chứa
thông tin các sách tìm kiếm được.
 private int Tongsoluongtk(DataView dv)
     {
         int s = 0;
         foreach (DataRow r in dv.ToTable("sach").Rows )
         {
            s += (int)r["soluong"];
         }
         return s;
     }
+ Sự kiện frmtimkiemsach_Load:Nạp thông tin của 4 quyển sách đầu tiên theo thứ
tự giảm dần của ngaynhap vào DataSet ds với tên bảng trong DataSet là sach, sau đó
hiển thị thông tin của bảng sach trong DataSet vào dataGridView1, đưa tổng số sách
và tổng số lượng sách trong DataSet vào thanh trạng thái
    private void frmtimkiemsach_Load(object sender, EventArgs e)
     {
         cn.Open(); // Mở kết nối
         cmd.CommandText = "select top 4 * from sach order by ngaynhap desc" ;
         cmd.Connection = cn;
         da.SelectCommand = cmd;
         da.Fill(ds, "sach"); // Nạp dữ liệu vào DataSet
         dataGridView1.DataSource = ds.Tables["sach"]; // Nạp dữ liệu vào dataGridView1
        // Nạp dữ liệu vào thanh trạng thái



                 Bài tập thực hành Chuyên đề Visual Studio .NET   105
thanhtrangthai.Items[0].Text  = "Tổng số sách:" +
                                         ds.Tables["sach"].Rows.Count.ToString();
        thanhtrangthai.Items[1].Text = "Tổng số lượng:" + Tongsoluong().ToString();
     }
+ Sự kiện txttimkiem_KeyPress: Khi người sử dụng nhấn Enter trên txttimkiem thì
việc tìm kiếm tương đối bắt đầu: Tạo ra 1 DataView dv chứa dữ liệu của bảng sách
trong DataSet ds, lọc trong DataView dv ra thông tin của các quyển sách gần giống với
dữ liệu nhập trên txttimkiem, sau đó đưa kết quả lọc ra trên dataGridView1 và thanh
trạng thái.

private void txttimkiem_KeyPress(object sender, KeyPressEventArgs e)
    {
       if (e.KeyChar == 13)
       {
          DataView dv = new DataView(ds.Tables["sach"]); //Nạp dữ liệu vào DataView
          //bắt đầu lọc dữ liệu
          dv.RowFilter = "nhande like '%" + txttimkiem.Text + "%' or tacgia like '%"
                                                         + txttimkiem.Text + "%'";
          dataGridView1.DataSource = dv; //Nạp kết quả lọc trong dv vào dataGridView1
          // Đưa số quyển sách và tổng số lượng sách lọc được vào thanh trang thái
             thanhtrangthai.Items[0].Text = "Số kết quả tìm thấy được: " +
                 dv.Count.ToString() + "/" + ds.Tables["sach"].Rows.Count.ToString();
             thanhtrangthai.Items[1].Text = "Tổng số lượng tìm thấy được:" +
                 Tongsoluongtk(dv).ToString() + "/" + Tongsoluong().ToString();
         }
    }

3.4.4 Cập nhật CSDL bằng DataAdapter
Sau khi DataAdapter đã nạp dữ liệu vào table, connection sẽ được đóng, và các thay
đổi sau đó đối sau đó tạo ra cho dữ liệu sẽ chỉ có ảnh hưởng trong DataSet chứ không
phải là ở dữ liệu nguồn! Để thực sự cập nhật các thay đổi này lên nguồn dữ liệu,
DataAdapter phải được sử dụng để khôi phục connection và gửi các dòng dữ liệu đã
được thay đổi lên CSDL.

      Ngoài SelectCommand, DataAdapter có thêm 3 thuộc tính Command nữa, gồm
InsertCommand, DeleteCommand và UpdateCommand, làm nhiệm vụ thực hiện các
thao tác tương ứng với tên thuộc tính của chúng (chèn, xóa, cập nhật). Các Command
này được thực thi khi phương thức Update của DataAdapter được triệu gọi. Khó khăn
nằm ở chỗ tạo ra các query command phức tạp này (cú pháp của câu lệnh SQL tương
ứng càng dài dòng và phức tạp khi số lượng column nhiều lên). Rất may là các data
provider đều có cài đặt một lớp gọi là CommandBuilder dùng để quản lý việc tạo các
Command nói trên một cách tự động.



                    Bài tập thực hành Chuyên đề Visual Studio .NET    106
3.4.4.1 CommandBuilder
Một đối tượng CommandBuilder sẽ sinh ra các Command cần thiết để thực hiện việc
cập nhật nguồn dữ liệu tạo ra bởi DataSet. Cách tạo đối tượng CommandBuilder là
truyền đối tượng DataAdapter cho phương thức khởi dựng của nó; sau đó, khi phương
thức DataAdapter.Update được gọi, các lệnh SQL sẽ được sinh ra và thực thi. Đoạn
code dưới đây minh họa cách thức thay đổi dữ liệu ở một DataTable và cập nhật lên
CSDL tương ứng bằng DataAdapter:

//Giả sử đã có 1 DataSet ds chứa dữ liệu của bảng khoa

DataTable dt= ds.Tables["khoa"];
// (1) Dùng commandBuilder để sinh ra các Command cần thiết để update
SqlCommandBuilder sb = new SqlCommandBuilder(da);

// (2) Thực hiện thay đổi dữ liệu: thêm 1 khoa mới
DataRow drow = dt.NewRow();
drow["Makhoa"] = 12;
drow["tenkhoa"] = "abc";
dt.Rows.Add(drow);

// (3) Thực hiện thay đổi dữ liệu: xóa 1 khoa
dt.Rows[4].Delete();

// (4) Thực hiện thay đổi dữ liệu: thay đổi giá trị 1 dòng dữ liệu
dt.Rows[5]["tenkhoa"] = "this must be changed";

// (5) Tiến hành cập nhật lên CSDL
int nUpdate = da.Update(ds, "khoa");

MessageBox.Show("Số dòng được thay đổi: " + nUpdate.ToString()); //  3

Có một số hạn chế khi sử dụng CommandBuilder: Command Select ứng với
DataAdapter chỉ được tham chiếu đến 1 table, và table nguồn trong CSDL phải bao
gồm một primary key hoặc một column chứa các giá trị duy nhất. Column này (hay tổ
hợp các columns) phải được bao gồm trong command Select ban đầu.

3.4.4.2 Đồng bộ hóa dữ liệu giữa DataSet và CSDL
Như đã minh họa trong ví dụ này, việc sử dụng DataAdapter làm đơn giản hóa và tự
động hóa quá trình cập nhật CSDL hoặc bất kỳ data source nào. Tuy nhiên, có một vấn
đề ở đây: multi-user (nhiều người sử dụng). Mô hình Ngắt kết nối được dựa trên cơ
chế Optimistic Concurrency, một cách tiếp cận mà trong đó các dòng dữ liệu ở data
source không bị khóa (lock) giữa thời gian mà chúng được đọc và thời gian mà các cập
nhật được áp dụng cho data source. Trong khoảng thời gian này, user khác có thể cũng




                  Bài tập thực hành Chuyên đề Visual Studio .NET     107
cập nhật data source. Nếu có thay đổi xảy ra kể từ lần đọc trước đó thì phương thức
Update sẽ nhận biết được và không cho áp dụng thay đổi đối với các dòng dữ liệu đó.

      Có hai phương án cơ bản để giải quyết lỗi concurrency (tương tranh) khi có
nhiều cập nhật được áp dụng: roll back tất cả các thay đổi nếu như xuất hiện xung đột
(violation), hoặc áp dụng các cập nhật không gây ra lỗi và xác định những cập nhật có
gây ra lỗi để có thể xử lý lại.

3.4.4.3 Sử dụng Transactions để Roll Back nhiều cập nhật
Khi thuộc tính DataAdapter.ContinueUpdateOnErrors được thiết lập là false, một
ngoại lệ sẽ được ném ra khi một thay đổi dòng dữ liệu không thể thực hiện được. Điều
này sẽ ngăn các cập nhật tiếp theo được thực thi, nhưng lại không ảnh hưởng đến các
cập nhật đã xuất hiện trước ngoại lệ đó. Do những cập nhật có thể có liên quan với
nhau, ứng dụng thường là cần chiến lược hoặc là tất cả, hoặc là không (all-or-none).
Cách dễ nhất để thực thi chiến lược này là tạo ra một transaction trong đó tất cả các
command update sẽ được thực thi. Để thực hiện điều này, tạo ra một đối tượng
SqlTransaction và gắn nó với SqlDataAdapter.SelectCommand bằng cách truyền nó
cho hàm khởi dựng của nó. Nếu có ngoại lệ xảy ra, phương thức Rollback sẽ được
thực thi để undo mọi thay đổi trước đó; nếu không có ngoại lệ nào xuất hiện, phương
thức Commit được thực thi để áp dụng tất cả các command update. Dưới đây là một ví
dụ:

SqlDataAdapter da = new SqlDataAdapter();

SqlCommandBuilder sb = new SqlCommandBuilder(da);

SqlTransaction tran;

SqlConnection conn = new SqlConnection(connStr);

conn.Open();     // Connection phải được dùng với Transaction

// (1) Tạo ra một transaction
SqlTransaction tran = conn.BeginTransaction();

// (2) Gắn SelectCommand với transaction
da.SelectCommand = new SqlCommand(sql, conn, tran);

DataSet ds = new DataSet();

da.Fill(ds, "docgia");

//
// Code ở phần này thực hiện các cập nhật lên các dòng dữ liệu ở DataSet
try



                  Bài tập thực hành Chuyên đề Visual Studio .NET   108
{
    int updates = da.Update(ds, "docgia");
    MessageBox.Show("Cập nhật: " + updates.ToString());
}
// (3) Nếu có ngoại lệ xuất hiện, roll back mọi cập nhật trong transaction
catch (Exception ex)
{
   MessageBox.Show(ex.Message); // Lỗi khi cập nhật
   if (tran != null)
   {
      tran.Rollback();  // Roll back mọi cập nhật
      tran = null;

        MessageBox.Show("Tất cả các cập nhật đã bị roll back.");
    }
}
finally
{
  // (4) Nếu không có lỗi, commit tất cả các cập nhật
  if (tran != null)
  {
      tran.Commit();
      MessageBox.Show("Tất cả đã được cập nhật thành công. ");
      tran = null;
  }
}

conn.Close();



3.4.4.4 Xác định các dòng gây ra lỗi cập nhật
Khi thuộc tính DataAdapter.ContinueUpdateOnErrors được thiết lập là True, các xử
lý sẽ không ngưng nếu có một dòng dữ liệu không thể cập nhật được. Thay vào đó,
DataAdapter sẽ cập nhật tất cả các dòng dữ liệu không gây ra lỗi. Sau đó, lập trình
viên có thể xác định các dòng dữ liệu không cập nhật được và quyết định cách xử lý
chúng.

     Các dòng dữ liệu không cập nhật được có thể dễ dàng được xác định qua thuộc
tính DataRowState của chúng (đã được trình bày trong phần mô tả DataRow). Các
dòng dữ liệu đã được cập nhật thành công sẽ có trạng thái Unchanged; trong khi đó
các dòng dữ liệu không cập nhật thành công sẽ mang trạng thái Added, Deleted hoặc




                     Bài tập thực hành Chuyên đề Visual Studio .NET   109
Modified. Đoạn code dưới đây minh họa cách lặp qua các dòng dữ liệu và xác định các
dòng chưa được cập nhật.

// SqlDataAdapter da nạp dữ liệu của bảng docgia
da.ContinueUpdateOnError = true;

DataSet ds = new DataSet();
try
{
  da.Fill(ds, "docgia");
  DataTable dt = ds.Tables["docgia"];

 SqlCommandBuilder sb = new SqlCommandBuilder(da);

 // ... Các thao tác cập nhật

 dt.Rows[29].Delete();                  // Delete

 dt.Rows[30]["HoTen"] = "try to change";            // Update

 dt.Rows[30][Madocgia] = 1234;              // Update

 dt.Rows[31]["HoTen"] = "XYZ";                 // Update

 DataRow drow = dt.NewRow();
 drow["HoTen"] = "tnv spider";
 drow["Madocgia"] = 25;
 dt.Rows.Add(drow);                     // insert

 // Submit updates
 int updates = da.Update(ds, "docgia");

 if (ds.HasChanges())
 {
    // Load rows that failed into a DataSet
    DataSet failures = ds.GetChanges();

   int rowsFailed = failures.Rows.Count;

   Console.WriteLine("Số dòng không thể cập nhật: " + rowsFailed);

   foreach (DataRow r in failures.Tables[0].Rows )
   {
     string state = r.RowState.ToString());




                  Bài tập thực hành Chuyên đề Visual Studio .NET   110
// Phải hủy bỏ thay đổi để hiển thị dòng đã bị xóa
         if (r.RowState == DataRowState.Deleted)
              r.RejectChanges();

         string iMadocgia= ((int)r["Madocgia"]).ToString();

         string msg = state + " Madocgia: " + iMadocgia;

         Console.WriteLine(msg);
     }
 }

Lưu ý rằng ngay cả khi thao tác xóa xuất hiện trước, nó cũng không có tác dụng đối
với các thao tác khác. Câu lệnh SQL xóa hay cập nhật một dòng dữ liệu được dựa theo
giá trị của primary key chứ không liên quan đến vị trí của nó. Ngoài ra các cập nhật
trên cùng một dòng được kết hợp và đếm như là 1 cập nhật cho dòng đó bởi phương
thức Update. Ở ví dụ trên, các cập nhật cho dòng 30 được tính như là 1 cập nhật.

3.4.5 Định nghĩa Relationships giữa các Table trong DataSet
Một DataRelation là một mối quan hệ parent-child giữa hai đối tượng DataTables. Nó
được định nghĩa dựa trên việc so khớp các columns trong 2 DataTable. Cú pháp hàm
khởi dựng là như sau:

public DataRelation(
 string relationName,
 DataColumn parentColumn,
 DataColumn childColumn)

Một DataSet có một thuộc tính Relations giúp quản lý tập hợp các DataRelation đã
được định nghĩa trong DataSet. Sử dụng phương thức Relations.Add để thêm các
DataRelation vào tập hợp Relations. Ví dụ dưới đây thiết lập mối quan hệ giữa hai
bảng khoa và docgia để có thể liệt kê danh sách các docgia của mỗi khoa.

string connStr="Data Source=tên máy chủ;Initial Catalog=quanlythuvien;
Trusted_Connection=yes";
DataSet ds = new DataSet();
// (1) Fill bảng docgia
string sql = "SELECT * FROM docgia”;
SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand();
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
da.Fill(ds, "docgia");
// (2) Fill bảng khoa
sql = "SELECT * FROM khoa”;



                     Bài tập thực hành Chuyên đề Visual Studio .NET   111
da.SelectCommand.CommandText = sql;
da.Fill(ds, "khoa");
// (3) Định nghĩa relationship giữa 2 bảng khoa và docgia
DataTable parent = ds.Tables["khoa"];
DataTable child = ds.Tables["docgia"];
DataRelation relation = new DataRelation("khoa_docgia", parent.Columns["makhoa"],

     child.Columns["makhoa"]);
// (4) Đưa relationship vào DataSet
ds.Relations.Add(relation);
// (5) Liệt kê danh sách các đọc giả của từng khoa
foreach (DataRow r in parent.Rows)
{
   Console.WriteLine(r["tenkhoa"]);        // Tên khoa
   foreach (DataRow rc in r.GetChildRows("khoa_docgia"))
   {
     Console.WriteLine(" " + rc["HoTen"]);
   }
}
/*
   Ví dụ kết quả:
     Khoa Tin
       Nguyễn Văn Trung
       Ngô Anh Tuấn
       Lê Thanh Hoa
     Khoa Toán
       Nguyễn Thị Hoa
       Trần Văn Phúc
*/
Khi một relationship được định nghĩa giữa 2 tables, nó cũng sẽ thêm một
ForeignKeyConstraint vào tập hợp Constraints của DataTable con. Constraint này
quyết định cách mà DataTable con bị ảnh hưởng khi các dòng dữ liệu ở phía
DataTable cha bị thay đổi hay bị xóa. Trong thực tế, điều này có nghĩa là khi bạn xóa 1
dòng trong DataTable cha, các dòng con có liên quan cũng bị xóa – hoặc cũng có thể
là các key bị đặt lại giá trị thành NULL. Tương tự như thế, nếu một giá trị key bị thay
đổi trong DataTable cha, các dòng dữ liệu có liên quan trong DataTable con cũng có
thể bị thay đổi theo hoặc bị đổi giá trị thành NULL.

     Các luật như trên được xác định bởi các thuộc tính DeleteRule và UpdateRule
của constraint. Các luật này được nhận các giá trị liệt kê sau đây:




                 Bài tập thực hành Chuyên đề Visual Studio .NET   112
-   Cascade. Xóa/Cập nhật các dòng dữ liệu có liên quan trong DataTable con. Đây
       là giá trị mặc định.
   -   None. Không làm gì.
   -   SetDefault. Thiết lập các giá trị khóa trong các dòng dữ liệu có liên quan của
       DataTable con thành giá trị mặc định của column tương ứng.
   -   SetNull. Thiết lập các giá trị khóa trong các dòng dữ liệu có liên quan của
       DataTable con thành null.


Xem xét ví dụ dưới đây:

// (1) Thêm một dòng với khóa mới vào DataTable con
DataRow row = child.NewRow();

row["Makhoa"] = 999; // giả sử trong bảng khoa không có record nào có Makhoa = 999

child.Rows.Add(row); // Không được do 999 không tồn tại trong DataTable cha

// (2) Xóa một dòng trong DataTable cha
row = parent.Rows[0];
row.Delete();     // Xóa các dòng trong DataTable con có khóa này

// (3) Tạm thời vô hiệu hóa constraints và thử thêm dòng mới
ds.EnforceConstraints = false;

row["Makhoa"] = 999;
child.Rows.Add(row); // Được chấp nhận!!!
ds.EnforceConstraints = true; // Kích hoạt constraint trở lại

// (4) Thay đổi constraint để đặt các dòng dữ liệu thành null nếu DataTable thay đổi
((ForeignKeyConstraint)child.Constraints[0]).DeleteRule = Rule.SetNull;

Lưu ý rằng thuộc tính EnforeceConstraint được đặt thành false sẽ làm vô hiệu hóa tất
cả các constraint – điều này trong thuật ngữ CSDL gọi là bỏ qua tính toàn vẹn tham
chiếu. Điều này cho phép một khoa được bổ sung vào thậm chí khi cả column Makhoa
không tương ứng với dòng nào trong bảng khoa. Nó cũng cho phép một dòng khoa
được xóa ngay cả khi có nhiều dòng tương ứng với nó trong bảng docgia.

3.5 Sử dụng Data Binding


3.6 Lựa chọn giữa mô hình Kết nối và mô hình Ngắt kết nối
DataReader và DataSet đưa ra hai cách tiếp cận khác nhau để xử lý dữ liệu.
DataReader cho phép truy xuất kiểu forward-only, read-only. Bằng cách xử lý từng



                  Bài tập thực hành Chuyên đề Visual Studio .NET      113
dòng một, cách tiếp cận này giúp tiết kiệm bộ nhớ. DataSet, ngược lại, cho phép truy
xuất theo kiểu read/write, nhưng lại yêu cầu phải có đủ bộ nhớ để quản lý bản sao dữ
liệu nạp về từ data source. Như vậy, bạn có thể suy ra một số quy tắc chung: Nếu ứng
dụng không cần tính năng cập nhật dữ liệu và hầu như chỉ hiển thị và chọn lọc dữ liệu,
DataReader là lựa chọn thích hợp; nếu ứng dụng cần cập nhật dữ liệu, giải pháp sử
dụng DataSet nên được xem xét.

      Tất nhiên, cũng có một số tình huống đi ngược lại với các quy tắc chung nói trên.
Chẳng hạn, nếu data source chứa một số lượng lớn các record, khi đó DataSet phải yêu
cầu quá nhiều tài nguyên bộ nhớ; hoặc nếu dữ liệu chỉ yêu cầu một vài thao tác cập
nhật, thì sự kết hợp giữa DataReader và Command để thực thi cập nhật sẽ có thể có ý
nghĩa hơn.

     Tóm lại, một DataSet là một lựa chọn tốt khi:

      -   Dữ liệu cần được serialize (tuần tự hóa) và/hoặc gửi đi bằng HTTP.
      -   Các điều khiển read-only trên Form Win Form được kết buộc (bind) với data
          source.
      -   Một điều khiển Win Form như GridView hay DataView được kết buộc với
          một data source có khả năng cập nhật được.
      -   Một ứng dụng desktop cần thêm, xóa, sửa các dòng dữ liệu.
     Trong khi đó, DataReader là lựa chọn cho những trường hợp:

      -   Cần quản lý một số lượng lớn các record, lớn đến mức mà bộ nhớ và thời
          gian để nạp dữ liệu cho DataSet là phi thực tế.
      -   Dữ liệu là read-only và được kết buộc với một điều khiển loại danh sách (list
          control) của Win Form hoặc Web Form.
      -   CSDL là không ổn định và thay đổi thường xuyên.




                  Bài tập thực hành Chuyên đề Visual Studio .NET   114
3.6 Tạo đối tượng DataSet
       Trong ví dụ trước, tạo ra đối tượng SqlDataAdapter bằng cách gắn trực tiếp
chuỗi kết nối và chuỗi truy vấn vào nó. Đối tượng Connection và Command sẽ được
tạo và tích hợp vào trong đối tượng DataAdapter này. Với cách này, ta sẽ bị hạn chế
trong các thao tác liên quan đến cơ sở dữ liệu.
SqlDataAdapter     DataAdapter          =     new       SqlDataAdapter(commandString,
connectionString);
Ví dụ sau đây sẽ minh họa việc lấy về đối tượng DataSet bằng cách tạo ra các đối
tượng Connection và Command một cách riêng biệt, khi ta cần dùng lại chúng hay
muốn thực hiện hoàn chỉnh một thao tác thì sẽ thuận lợi hơn.
Đầu tiên ta sẽ khai báo bốn biến thành viên thuộc lớp, như sau:
private System.Data.SqlClient.SqlConnection myConnection;
private System.Data.DataSet myDataSet;
private System.Data.SqlClient.SqlCommand myCommand;
private System.Data.SqlClient.SqlDataAdapter DataAdapter;
Đối tượng Connection sẽ được tạo riêng với chuỗi kết nối:
string connectionString = "server=localhost; uid=sa; pwd=; database=northwind";
myConnection = new System.Data.Sql.SqlConnection(connectionString);
Sau đó ta sẽ mở kết nối: myConnection.Open( );
Ta có thể thực hiện nhiều giao tác trên cơ sở dữ liệu khi kết nối được mở và sau khi
dùng xong ta chỉ đơn giản đóng kết nối lại. Tiếp theo ta sẽ tạo ra đối tượng DataSet:
myDataSet = new System.Data.DataSet( );
Và tiếp tục tạo đối tượng Command, gắn cho nó đối tượng Connection đã mở và chuỗi
truy vấn dữ liệu:
myCommand = new System.Data.SqlClient.SqlCommand( )
myCommand.Connection=myConnection;
myCommand.CommandText = "Select * from Customers";
Cuối cùng ta cần tạo ra đối tượng SqlDataAdapter, gắn đối tượng SqlCommand vừa
tạo ở trên cho nó, đồng thời phải tiến hành ánh xạ bảng dữ liệu nó nhận được từ câu
truy vấn của đối tượng Command để tạo sự đồng nhất về tên các cột khi đẩy bảng dữ
liệu này vào DataSet.
DataAdapter = new System.Data.SqlClient.SqlDataAdapter( );



                 Bài tập thực hành Chuyên đề Visual Studio .NET   115
DataAdapter.SelectCommand= myCommand;
DataAdapter.TableMappings.Add("Table","Customers");
DataAdapter.Fill(myDataSet);
Bây giờ ta chỉ việc gắn DataSet vào thuộc tính DataSoucre của điều khiển lưới:
dataGrid1.DataSource=myDataSet.Tables["Customers"].DefaultView;
Dưới đây là mã hoàn chỉnh của ứng dụng này:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace ProgrammingCSharpWindows.Form
{
public class ADOForm1: System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
private System.Windows.Forms.DataGrid dataGrid1;
private System.Data.SqlClient.SqlConnection myConnection;
private System.Data.DataSet myDataSet;
private System.Data.SqlClient.SqlCommand myCommand;
private System.Data.SqlClient.SqlDataAdapter DataAdapter;
public ADOForm1( )
{
InitializeComponent( );
// tạo đối tượng connection và mở nó
string connectionString =
"server=Neptune; uid=sa; pwd=oWenmEany;" +
"database=northwind";



                 Bài tập thực hành Chuyên đề Visual Studio .NET   116
myConnection = new SqlConnection(connectionString);
myConnection.Open();
// tạo đối tượng DataSet mới
myDataSet = new DataSet( );
// tạo đối tượng command mới và gắn cho đối tượng
// connectio và chuỗi truy vấn cho nó
myCommand = new System.Data.SqlClient.SqlCommand( );
myCommand.Connection=myConnection;
myCommand.CommandText = "Select * from Customers";
// tạo đối tượng DataAdapter với đối tượng Command vừa
// tạo ở trên, đồng thời thực hiện ánh xạ bảng dữ liệu
DataAdapter = new SqlDataAdapter( );
DataAdapter.SelectCommand= myCommand;
DataAdapter.TableMappings.Add("Table","Customers");
// đẩy dữ liệu vào DataSet
DataAdapter.Fill(myDataSet);
// gắn dữ liệu vào lưới
dataGrid1.DataSource =
myDataSet.Tables["Customers"].DefaultView;
}
public override void Dispose()
{
base.Dispose();
components.Dispose();
}
private void InitializeComponent( )
{
this.components = new System.ComponentModel.Container();
this.dataGrid1 = new System.Windows.Forms.DataGrid();
dataGrid1.BeginInit();



                  Bài tập thực hành Chuyên đề Visual Studio .NET   117
dataGrid1.Location = new System.Drawing.Point(24, 32);
dataGrid1.Size = new System.Drawing.Size(480, 408);
dataGrid1.DataMember = "";
dataGrid1.TabIndex = 0;
this.Text = "ADOFrm1";
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(536, 501);
this.Controls.Add(this.dataGrid1);
dataGrid1.EndInit( );
}
public static void Main(string[] args)
{
Application.Run(new ADOForm1());
}
}
}
Giao diện của ví dụ này cũng tương tự như các ví dụ trên.
3.7 Kết hợp giữa nhiều bảng
Các ví dụ ở trên chỉ đơn thuần lấy dữ liệu từ trong một bảng. Ở ví dụ này ta sẽ tìm
hiểu về cách lấy dữ liệu trên hai bảng. Trong cơ sở dữ liệu của ta, một khách hàng có
thể có nhiều hóa đơn khác nhau, vì thế ta sẽ có quan hệ một nhiều giữa bảng khách
hàng (Customers)và bảng hóa đơn (Orders). Bảng Orders sẽ chứa thuộc tính
CustomersId của bảng Customers, thuộc tính này đóng vai trò là khóa chính đối bảng
Customers và khóa ngoại đối với bảng Orders.
Ứng dụng của ta sẽ hiển thị dữ liệu của hai bảng Customers và Orders trên cùng một
lưới và thể hiện quan hệ một nhiều của hai bảng ngay trên lưới. Để làm được điều này
ta chỉ cần dùng chung một đối tượng Connetion, hai đối tượng tượng SqlDataAdapter
và hai đối tượng SqlCommand.
Sau khi tạo đối tượng SqlDataAdapter cho bảng Customers tương tự như ví dụ trên, ta
tiến tạo tiếp đối tượng SqlDataAdapter cho bảng Orders:
myCommand2 = new System.Data.SqlClient.SqlCommand();
DataAdapter2 = new System.Data.SqlClient.SqlDataAdapter();
myCommand2.Connection = myConnection;


                  Bài tập thực hành Chuyên đề Visual Studio .NET   118
myCommand2.CommandText = "SELECT * FROM Orders";
Lưu ý là ở đây đối tượng DataAdapter2 có thể dùng chung đối tượng Connection ở
trên, nhưng đối tượng Command thì khác. Sau đó gắn đối tượng Command2 cho
DataAdapter2, ánh xạ bảng dữ liệu và đẩy dữ liệu vào DataSet ở trên.
DataAdapter2.SelectCommand = myCommand2;
DataAdapter2.TableMappings.Add ("Table", "Orders");
DataAdapter2.Fill(myDataSet);
Tại thời điểm này, ta có một đối tượng DataSet nhưng chứa hai bảng dữ liệu:
Customers và Orders. Do ta cần thể hiện cả quan hệ của hai bảng ngay trên điều khiển
lưới, cho nên ta cần phải định nghĩa quan hệ này cho đối tượng DataSet của chúng ta.
Nếu không làm điều này thì đối tượng DataSet sẽ bỏ qua quan hệ giữa 2 bảng này.
Do đó ta cần khai báo thêm đối tương DataRelation:
System.Data.DataRelation dataRelation;
Do mỗi bảng Customers và Orders đều có chứa một thuộc tính CustomersId, nên ta
cũng cần khái báo thêm hai đối tượng DataColumn tương ứng với hai thuộc tính này.
System.Data.DataColumn dataColumn1;
System.Data.DataColumn dataColumn2;
Mỗi một DataColumn sẽ giữ giá trị của một cột trong bảng của đối tượng DataSet:
dataColumn1 = myDataSet.Tables["Customers"].Columns["CustomerID"];
dataColumn2 = myDataSet.Tables["Orders"].Columns["CustomerID"];
Ta tiến hành tạo quan hệ cho hai bảng bằng cách gọi hàm khởi tạo của đối tượng
DataRelation, truyền vào cho nó tên quan hệ và hai cột cần tạo quan hệ:
dataRelation = new System.Data.DataRelation("CustomersToOrders",
dataColumn1, dataColumn2);
Sau khi tạo được đối tượng DataRelation, ta thêm vào DataSet của ta. Sau đó ta cần
tạo một đối tượng quản lý khung nhìn DataViewManager cho DataSet, đối tượng
khung nhìn này sẽ được gán cho lưới điều khiển để hiển thị:
myDataSet.Relations.Add(dataRelation);
DataViewManager DataSetView = myDataSet.DefaultViewManager;
dataGrid1.DataSource = DataSetView;
Do điều khiển lưới phải hiển thị quan hệ của hai bảng dữ liệu, nên ta phải chỉ cho nó
biết là bảng nào sẽ là bảng cha. Ở đây bảng cha là bảng Customers:
dataGrid1.DataMember= "Customers";



                 Bài tập thực hành Chuyên đề Visual Studio .NET   119
Sau đây là mã hoàn chỉnh của toàn bộ ứng dụng:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace ProgrammingCSharpWindows.Form
{
public class ADOForm1: System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
private System.Windows.Forms.DataGrid dataGrid1;
private System.Data.SqlClient.SqlConnection myConnection;
private System.Data.DataSet myDataSet;
private System.Data.SqlClient.SqlCommand myCommand;
private System.Data.SqlClient.SqlCommand myCommand2;
private System.Data.SqlClient.SqlDataAdapter DataAdapter;
private System.Data.SqlClient.SqlDataAdapter DataAdapter2;
public ADOForm1( )
{
InitializeComponent( );
// tạo kết nối
string connectionString = "server=Neptune; uid=sa;" +
" pwd=oWenmEany; database=northwind";
myConnection = new SqlConnection(connectionString);
myConnection.Open( );
// tạo DataSet
myDataSet = new System.Data.DataSet( );



                 Bài tập thực hành Chuyên đề Visual Studio .NET   120
// tạo đối tượng Command và DataSet cho bảng Customers
myCommand = new System.Data.SqlClient.SqlCommand( );
myCommand.Connection=myConnection;
myCommand.CommandText = "Select * from Customers";
DataAdapter =new System.Data.SqlClient.SqlDataAdapter();
DataAdapter.SelectCommand= myCommand;
DataAdapter.TableMappings.Add("Table","Customers");
DataAdapter.Fill(myDataSet);
// tạo đối tượng Command và DataSet cho bảng Orders
myCommand2 = new System.Data.SqlClient.SqlCommand( );
DataAdapter2=new System.Data.SqlClient.SqlDataAdapter();
myCommand2.Connection = myConnection;
myCommand2.CommandText = "SELECT * FROM Orders";
DataAdapter2.SelectCommand = myCommand2;
DataAdapter2.TableMappings.Add ("Table", "Orders");
DataAdapter2.Fill(myDataSet);
// thiết lập quan hệ giữa 2 bảng
System.Data.DataRelation dataRelation;
System.Data.DataColumn dataColumn1;
System.Data.DataColumn dataColumn2;
dataColumn1 =
myDataSet.Tables["Customers"].Columns["CustomerID"];
dataColumn2 =
myDataSet.Tables["Orders"].Columns["CustomerID"];
dataRelation = new System.Data.DataRelation(
"CustomersToOrders", dataColumn1, dataColumn2);
// thêm quan hệ trên vào DataSet
myDataSet.Relations.Add(dataRelation);
// Đặt khung nhìn và bảng hiển thị trước cho lưới
DataViewManager DataSetView =



                  Bài tập thực hành Chuyên đề Visual Studio .NET   121
myDataSet.DefaultViewManager;
dataGrid1.DataSource = DataSetView;
dataGrid1.DataMember= "Customers";
}
public override void Dispose( )
{
base.Dispose( );
components.Dispose( );
}
private void InitializeComponent( )
{
this.components = new System.ComponentModel.Container();
this.dataGrid1 = new System.Windows.Forms.DataGrid();
dataGrid1.BeginInit( );
dataGrid1.Location = new System.Drawing.Point(24, 32);
dataGrid1.Size = new System.Drawing.Size(480, 408);
dataGrid1.DataMember = "";
dataGrid1.TabIndex = 0;
this.Text = "ADOFrm1";
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size (536, 501);
this.Controls.Add (this.dataGrid1);
dataGrid1.EndInit ( );
}
public static void Main(string[] args)
{
Application.Run(new ADOForm1( ));
}
}
}



                   Bài tập thực hành Chuyên đề Visual Studio .NET   122
3.8 Thay đổi các bản ghi của cơ sở dữ liệu
Tới lúc này, chúng ta đã học cách lấy dữ liệu từ cơ sở dữ liệu sau đó hiển thị chúng ra
màn hình dựa vào các điều khiển có hay không kết buộc dữ liệu. Phần này chúng ta sẽ
tìm hiểu cách cập nhật vào cơ sở dữ liệu. Các thao tác trên cơ sở dữ liệu như: Thêm,
xóa và sửa một dòng trong các bảng dữ liệu. Sau đây là luồng công việc hoàn chỉnh
khi ta có một thao tác cập nhật cơ sở dữ liệu:
1. Đẩy dữ liệu của bảng vào DataSet bằng câu truy vấn SQL hay gọi thủ tục từ cơ sở
dữ liệu
2. Hiển thị dữ liệu trong các bảng có trong DataSet bằng cách kết buộc hay duyệt qua
các dòng dữ liệu.
3. Hiệu chỉnh dữ liệu trong các bảng DataTable với các thao tác thêm, xóa hay sửa trên
dòng DataRow.
4. Gọi phương thúc GetChanges() để lấy về một DataSet khác chứa tất cả các thay đổi
trên dữ liệu.
5. Kiểm tra lỗi trên DataSet mới được tạo này bằng thuộc tính HasErrors. Nếu có lỗi
thì ta sẽ tiến hành kiểm tra trên từng bảng DataTable của DataSet, khi gặp một bảng có
lỗi thì ta tiếp tục dùng hàm GetErrors()để lấy về các dòng DataRow có lỗi, ứng với
từng dòng ta sẽ dùng thuộc tính RowError trên dòng để xác định xem dòng đó có lỗi
hay không để có thể đưa ra xử lý thích hợp.
6. Trộn hai DataSet lại thành một.
7. Gọi phương thức Update() của đối tượng DataAdapter với đối số truyền vào là
DataSet vừa có trong thao tác trộn ở trên để cập nhật các thay đổi vào cơ sở dữ liệu.
8. Gọi phương thức AcceptChanges() của DataSet để cập nhật các thay đổi vào
DataSet này hay phương thức RejectChanges() nếu từ chối cập nhật thay đổi cho
DataSet hiện hành.
Với luồng công việc trên, cho phép ta có thể kiểm soát tốt được việc thay đổi trên cơ
sở dữ liệu hay việc gỡ lỗi cũng thuận tiện hơn. Trong ví dụ dưới đây , ta sẽ cho hiện
thị dữ liệu trong bảng Customers lên một ListBox, sau đó ta tiến hành các thao tác
thêm, xóa hay sửa trên cơ sở dữ liệu. Để dễ hiểu, ta giảm bớt một số thao tác quản lý
ngoại lệ hay lỗi, chỉ tập trung vào mục đích chính của ta. Giao diện chính của ứng
dụng sau khi hoàn chỉnh:
Trong Form này, ta có một ListBox lbCustomers liệt kê các khách hàng, một Button
btnUpdate cho việc cập nhật dữ liệu, một Button Xóa, ứng với nút thêm mới ta có tám
hộp thoại TextBox để nhận dữ liệu gõ vào từ người dùng. Đồng thời ta có thêm một
lblMessage để hiển thị các thông báo ứng với các thao tác trên.




                 Bài tập thực hành Chuyên đề Visual Studio .NET   123
3.9 Truy cập và hiển thị dữ liệu
Ta sẽ tạo ra ba biến thành viên: DataAdapter, DataSet và Command:
private SqlDataAdapter DataAdapter;
private DataSet DataSet;
private DataTable dataTable;
Việc khai báo các biến thành viên như vậy sẽ giúp ta có thể dùng lại cho các phương
thức khác nhau. Ta khai báo chuỗi kết nối và truy vấn:
string connectionString =
"server=localhost; uid=sa; pwd=; database=northwind";
string commandString = "Select * from Customers";
Các chuỗi được dùng làm đối số để tạo đối tượng DataAdapter:
DataAdapter=new SqlDataAdapter(commandString,ConnectionString);
Tạo ra đối tượng DataSet mới, sau đó đẩy dữ liệu từ DataAdapter vào cho nó:
DataSet = new DataSet();
DataAdapter.Fill(DataSet,"Customers");
Để hiển thị dữ liệu, ta sẽ gọi hàm PopulateDB()để đẩy dữ liệu vào ListBox:
dataTable = DataSet.Tables[0];
lbCustomers.Items.Clear( );
foreach (DataRow dataRow in dataTable.Rows)
{
lbCustomers.Items.Add(dataRow["CompanyName"]                      +     "    ("   +
dataRow["ContactName"] + ")" );
}
3.10 Cập nhật một dòng dữ liệu
Khi người dùng nhấn Button Update (cập nhật), ta sẽ lấy chỉ mục được chọn trên
ListBox, và lấy ra dòng dữ liệu DataRow trong bảng ứng với chỉ mục trên. Sau đó cập
nhật DataSet với dòng dữ liệu mới này nếu sau khi kiểm tra thấy chúng không có lỗi
nào cả. Chi tiết về quá trình thực hiện cập nhật:
Đầu tiên ta sẽ lấy về dòng dữ liệu người dùng muốn thay đổi từ đối tượng dataTable
mà ta đã khai báo làm biến thành viên ngay từ đầu:
DataRow targetRow = dataTable.Rows[lbCustomers.SelectedIndex];




                 Bài tập thực hành Chuyên đề Visual Studio .NET       124
Hiển thị chuỗi thông báo cập nhật dòng dữ liệu đó cho người dùng biết. Để làm điều
này ta sẽ gọi phương thức tình DoEvents() của đối tượng Application, hàm này sẽ giúp
sơn mới lại màn hình với thông điệp hay các thay đổi khác.
lblMessage.Text = "Updating " + targetRow["CompanyName"];
Application.DoEvents();
Gọi hàm BeginEdit() của đối tượng DataRow, để chuyển dòng dữ liệu sang chế độ
hiệu chỉnh ( Edit ) và EndEdit()để kết thúc chế độ hiệu chỉnh dòng.
targetRow.BeginEdit();
targetRow["CompanyName"] = txtCustomerName.Text;
targetRow.EndEdit();
Lấy về các thay đổi trên đối tượng DataSet để kiểm tra xem các thay đổi có xảy ra bất
kỳ lỗi nào không. Ở đây ta sẽ dùng một biến cờ có kiểu true/false để xác định là có lỗi
là true, không có lỗi là false.Kiểm tra lỗi bằng cách dùng hai vòng lặp tuần tự trên
bảng và dòng của DataSet mới lấy về ở trên, ta dùng thuộc tính
HasErrors để kiểm tra lỗi trên bảng, phương thức GetErrors()để lấy về các dòng có lỗi
trong bảng.
DataSet DataSetChanged;
DataSetChanged = DataSet.GetChanges(DataRowState.Modified);
bool okayFlag = true;
if (DataSetChanged.HasErrors)
{
okayFlag = false;
string msg = "Error in row with customer ID ";
foreach (DataTable theTable in DataSetChanged.Tables)
{
if (theTable.HasErrors)
{
DataRow[] errorRows = theTable.GetErrors( );
foreach (DataRow theRow in errorRows)
msg = msg + theRow["CustomerID"];
}
}



                    Bài tập thực hành Chuyên đề Visual Studio .NET   125
lblMessage.Text = msg;
}
Nếu biến cờ okagFlag là true,thì ta sẽ trộn DataSet ban đầu với DataSet thay đổi thành
một, sau đó cập nhật DataSet sau khi trộn này vào cơ sở dữ liệu.
if (okayFlag)
{
DataSet.Merge(DataSetChanged);
DataAdapter.Update(DataSet,"Customers");
Tiếp theo hiển thị câu lệnh truy vấn cho người dùng biết, và cập nhật những thay đổi
cho DataSet đầu tiên, rồi hiển thị dữ liệu mới lên đối tượng ListBox.
lblMessage.Text = DataAdapter.UpdateCommand.CommandText;
Application.DoEvents( );
DataSet.AcceptChanges( );
PopulateLB( );
Nếu cờ okayFlag là false, có nghĩa là có lỗi trong quá trình hiệu chỉnh dữ liệu, ta sẽ từ
chối các thay đổi trên DataSet.
else
DataSet.RejectChanges( );
3.11 Xóa một dòng dữ liệu
Mã thực thi của sự kiện xóa thì đơn giản hơn một chút, ta nhận về dòng cần xóa:
DataRow targetRow = dataTable.Rows[lbCustomers.SelectedIndex];
Giữ lại dòng cần xóa để dùng làm thông điệp hiển thị cho người dùng biết trước khi
xóa dòng này khỏi cơ sở dữ liệu.
string msg = targetRow["CompanyName"] + " deleted. ";,
Bắt đầu thực hiện xóa trên bảng dữ liệu, cập nhật thay đổi vào DataSet và cập nhật
luôn vào cơ sở dữ liệu:
dataTable.Rows[lbCustomers.SelectedIndex].Delete( );
DataSet.AcceptChanges( );
DataAdapter.Update(DataSet,"Customers");
Khi gọi hàm AccceptChanges()để cập nhật thay đổi cho DataSet thì nó sẽ lần lượt gọi
hàm này cho các DataTable, sau đó cho các DataRow để cập nhật chúng. Ta cũng cần




                  Bài tập thực hành Chuyên đề Visual Studio .NET   126
chú ý khi gọi hàm xóa trên bảng Customers, dòng dữ liệu DataRow của khách hàng
này chỉ được xóa nếu nó không vi phạm ràng buộc trên các bảng khác,
ở đây khách hàng chỉ được xóa nếu nếu khách hàng không có một hóa đơn nào trên
bảng Orders. Nếu có ta phải tiến hành xóa trên bảng hóa đơn trước, sau đó mới xóa
trên bảng Customers.
3.12 Tạo một dòng dữ liệu mới
Sau khi người dùng cung cấp các thông tin về khách hàng cần tạo mới và nhấn Button
tạo mới ( New ), ta sẽ viết mã thực thi trong hàm bắt sự kiện nhấn nút tạo mới này.
Đầu tiên ta sẽ tạo ra một dòng mới trên đối tượng DataTable, sau đó gán dữ liệu trên
các TextBox cho các cột của dòng mới này:
DataRow newRow = dataTable.NewRow( );
newRow["CustomerID"] = txtCompanyID.Text;
newRow["CompanyName"] = txtCompanyName.Text;
newRow["ContactName"] = txtContactName.Text;
newRow["ContactTitle"] = txtContactTitle.Text;
newRow["Address"] = txtAddress.Text;
newRow["City"] = txtCity.Text;
newRow["PostalCode"] = txtZip.Text;
newRow["Phone"] = txtPhone.Text;
Thêm dòng mới với dữ liệu vào bảng DataTable, cập nhật vào cơ sở dữ liệu, hiển thị
câu truy vấn, cập nhật DataSet, hiển thị dữ liệu mới lên hộp ListBox. Làm trắng các
điều khiển TextBox bằng hàm thành viên ClearFields().
dataTable.Rows.Add(newRow);
DataAdapter.Update(DataSet,"Customers");
lblMessage.Text = DataAdapter.UpdateCommand.CommandText;
Application.DoEvents( );
DataSet.AcceptChanges( );
PopulateLB( );
ClearFields( );
Để hiểu rõ hoàn chỉnh ứng, ta sẽ xem mã hoàn chỉnh của toàn ứng dụng:
using System;
using System.Drawing;



                  Bài tập thực hành Chuyên đề Visual Studio .NET   127
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace ProgrammingCSharpWindows.Form
{
public class ADOForm1: System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
private System.Windows.Forms.Label label9;
private System.Windows.Forms.TextBox txtPhone;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.TextBox txtContactTitle;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.TextBox txtZip;
private System.Windows.Forms.Label label6;
private System.Windows.Forms.TextBox txtCity;
private System.Windows.Forms.Label label5;
private System.Windows.Forms.TextBox txtAddress;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TextBox txtContactName;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox txtCompanyName;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox txtCompanyID;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button btnNew;
private System.Windows.Forms.TextBox txtCustomerName;
private System.Windows.Forms.Button btnUpdate;



                Bài tập thực hành Chuyên đề Visual Studio .NET   128
private System.Windows.Forms.Label lblMessage;
private System.Windows.Forms.Button btnDelete;
private System.Windows.Forms.ListBox lbCustomers;
private SqlDataAdapter DataAdapter;
// biết thành viên DataSet và dataTable cho phép ta sử
// dụng trên nhiều hàm khác nhau
private DataSet DataSet;
private DataTable dataTable;
public ADOForm1( )
{
InitializeComponent( );
string connectionString = "server=Neptune; uid=sa;" +
" pwd=oWenmEany; database=northwind";
string commandString = "Select * from Customers";
DataAdapter =
new SqlDataAdapter(commandString, connectionString);
DataSet = new DataSet( );
DataAdapter.Fill(DataSet,"Customers");
PopulateLB( );
}
// Đẩy dữ liệu vào điều khiển ListBox
private void PopulateLB( )
{
dataTable = DataSet.Tables[0];
lbCustomers.Items.Clear( );
foreach (DataRow dataRow in dataTable.Rows)
{
lbCustomers.Items.Add( dataRow["CompanyName"] + " (" +
dataRow["ContactName"] + ")" );
}



                 Bài tập thực hành Chuyên đề Visual Studio .NET   129
}
public override void Dispose( )
{
base.Dispose( );
components.Dispose( );
}
private void InitializeComponent( )
{
this.components = new System.ComponentModel.Container();
this.txtCustomerName=new System.Windows.Forms.TextBox();
this.txtCity = new System.Windows.Forms.TextBox();
this.txtCompanyID = new System.Windows.Forms.TextBox();
this.lblMessage = new System.Windows.Forms.Label();
this.btnUpdate = new System.Windows.Forms.Button();
this.txtContactName= new System.Windows.Forms.TextBox();
this.txtZip = new System.Windows.Forms.TextBox();
this.btnDelete = new System.Windows.Forms.Button();
this.txtContactTitle=new System.Windows.Forms.TextBox();
this.txtAddress = new System.Windows.Forms.TextBox();
this.txtCompanyName=new System.Windows.Forms.TextBox( );
this.label5 = new System.Windows.Forms.Label( );
this.label6 = new System.Windows.Forms.Label( );
this.label7 = new System.Windows.Forms.Label( );
this.label8 = new System.Windows.Forms.Label( );
this.label9 = new System.Windows.Forms.Label( );
this.label4 = new System.Windows.Forms.Label( );
this.lbCustomers = new System.Windows.Forms.ListBox( );
this.txtPhone = new System.Windows.Forms.TextBox( );
this.btnNew = new System.Windows.Forms.Button( );
this.label1 = new System.Windows.Forms.Label( );



                   Bài tập thực hành Chuyên đề Visual Studio .NET   130
this.label2 = new System.Windows.Forms.Label( );
this.label3 = new System.Windows.Forms.Label( );
txtCustomerName.Location =
new System.Drawing.Point(256, 120);
txtCustomerName.TabIndex = 4;
txtCustomerName.Size = new System.Drawing.Size(160, 20);
txtCity.Location = new System.Drawing.Point(384, 245);
txtCity.TabIndex = 15;
txtCity.Size = new System.Drawing.Size (160, 20);
txtCompanyID.Location =
new System.Drawing.Point (136, 216);
txtCompanyID.TabIndex = 7;
txtCompanyID.Size = new System.Drawing.Size (160, 20);
lblMessage.Location = new System.Drawing.Point(32, 368);
lblMessage.Text = "Press New, Update or Delete";
lblMessage.Size = new System.Drawing.Size (416, 48);
lblMessage.TabIndex = 1;
btnUpdate.Location = new System.Drawing.Point (32, 120);
btnUpdate.Size = new System.Drawing.Size (75, 23);
btnUpdate.TabIndex = 0;
btnUpdate.Text = "Update";
btnUpdate.Click +=
new System.EventHandler (this.btnUpdate_Click);
txtContactName.Location =
new System.Drawing.Point(136, 274);
txtContactName.TabIndex = 11;
txtContactName.Size = new System.Drawing.Size (160, 20);
txtZip.Location = new System.Drawing.Point (384, 274);
txtZip.TabIndex = 17;
txtZip.Size = new System.Drawing.Size (160, 20);



                Bài tập thực hành Chuyên đề Visual Studio .NET   131
btnDelete.Location = new System.Drawing.Point(472, 120);
btnDelete.Size = new System.Drawing.Size(75, 23);
btnDelete.TabIndex = 2;
btnDelete.Text = "Delete";
btnDelete.Click +=
new System.EventHandler (this.btnDelete_Click);
txtContactTitle.Location =
new System.Drawing.Point(136, 303);
txtContactTitle.TabIndex = 19;
txtContactTitle.Size = new System.Drawing.Size(160, 20);
txtAddress.Location = new System.Drawing.Point(384, 216);
txtAddress.TabIndex = 13;
txtAddress.Size = new System.Drawing.Size (160, 20);
txtCompanyName.Location= new System.Drawing.Point (136, 245);
txtCompanyName.TabIndex = 9;
txtCompanyName.Size = new System.Drawing.Size (160, 20);
label5.Location = new System.Drawing.Point (320, 252);
label5.Text = "City";
label5.Size = new System.Drawing.Size (48, 16);
label5.TabIndex = 14;
label6.Location = new System.Drawing.Point (320, 284);
label6.Text = "Zip";
label6.Size = new System.Drawing.Size (40, 16);
label6.TabIndex = 16;
label7.Location = new System.Drawing.Point (40, 312);
label7.Text = "Contact Title";
label7.Size = new System.Drawing.Size (88, 16);
label7.TabIndex = 18;
label8.Location = new System.Drawing.Point (320, 312);
label8.Text = "Phone";



                 Bài tập thực hành Chuyên đề Visual Studio .NET   132
label8.Size = new System.Drawing.Size (56, 16);
label8.TabIndex = 20;
label9.Location = new System.Drawing.Point (120, 120);
label9.Text = "New Customer Name:";
label9.Size = new System.Drawing.Size (120, 24);
label9.TabIndex = 22;
label4.Location = new System.Drawing.Point (320, 224);
label4.Text = "Address";
label4.Size = new System.Drawing.Size (56, 16);
label4.TabIndex = 12;
lbCustomers.Location = new System.Drawing.Point(32, 16);
lbCustomers.Size = new System.Drawing.Size (512, 95);
lbCustomers.TabIndex = 3;
txtPhone.Location = new System.Drawing.Point (384, 303);
txtPhone.TabIndex = 21;
txtPhone.Size = new System.Drawing.Size (160, 20);
btnNew.Location = new System.Drawing.Point (472, 336);
btnNew.Size = new System.Drawing.Size (75, 23);
btnNew.TabIndex = 5;
btnNew.Text = "New";
btnNew.Click += new System.EventHandler(this.btnNew_Click);
label1.Location = new System.Drawing.Point (40, 224);
label1.Text = "Company ID";
label1.Size = new System.Drawing.Size (88, 16);
label1.TabIndex = 6;
label2.Location = new System.Drawing.Point (40, 252);
label2.Text = "Company Name";
label2.Size = new System.Drawing.Size (88, 16);
label2.TabIndex = 8;
label3.Location = new System.Drawing.Point (40, 284);



                 Bài tập thực hành Chuyên đề Visual Studio .NET   133
label3.Text = "Contact Name";
label3.Size = new System.Drawing.Size (88, 16);
label3.TabIndex = 10;
this.Text = "Customers Update Form";
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size (584, 421);
this.Controls.Add (this.label9);
this.Controls.Add (this.txtPhone);
this.Controls.Add (this.label8);
this.Controls.Add (this.txtContactTitle);
this.Controls.Add (this.label7);
this.Controls.Add (this.txtZip);
this.Controls.Add (this.label6);
this.Controls.Add (this.txtCity);
this.Controls.Add (this.label5);
this.Controls.Add (this.txtAddress);
this.Controls.Add (this.label4);
this.Controls.Add (this.txtContactName);
this.Controls.Add (this.label3);
this.Controls.Add (this.txtCompanyName);
this.Controls.Add (this.label2);
this.Controls.Add (this.txtCompanyID);
this.Controls.Add (this.label1);
this.Controls.Add (this.btnNew);
this.Controls.Add (this.txtCustomerName);
this.Controls.Add (this.btnUpdate);
this.Controls.Add (this.lblMessage);
this.Controls.Add (this.btnDelete);
this.Controls.Add (this.lbCustomers);
}



                  Bài tập thực hành Chuyên đề Visual Studio .NET   134
// Quản lý sự kiện nhấn nút tạo mới (New)
protected void btnNew_Click( object sender, System.EventArgs e)
{
// tạo một dòng mới
DataRow newRow = dataTable.NewRow( );
newRow["CustomerID"] = txtCompanyID.Text;
newRow["CompanyName"] = txtCompanyName.Text;
newRow["ContactName"] = txtContactName.Text;
newRow["ContactTitle"] = txtContactTitle.Text;
newRow["Address"] = txtAddress.Text;
newRow["City"] = txtCity.Text;
newRow["PostalCode"] = txtZip.Text;
newRow["Phone"] = txtPhone.Text;
// thêm một dòng mới vào bảng
dataTable.Rows.Add(newRow);
// cập nhật vào cơ sở dữ liệu
DataAdapter.Update(DataSet,"Customers");
// thông báo cho người dùng biết câu truy vấn thay đổi
lblMessage.Text = DataAdapter.UpdateCommand.CommandText;
Application.DoEvents( );
DataSet.AcceptChanges( );
// hiển thị lại dữ liệu cho điều khiển ListBox
PopulateLB( );
// Xoá trằng các TextBox
ClearFields( );
}
// Xóa trắng các TextBox
private void ClearFields( )
{
txtCompanyID.Text = "";



                  Bài tập thực hành Chuyên đề Visual Studio .NET   135
txtCompanyName.Text = "";
txtContactName.Text = "";
txtContactTitle.Text = "";
txtAddress.Text = "";
txtCity.Text = "";
txtZip.Text = "";
txtPhone.Text = "";
}
// quản lý sự kiện nhất nút chọn cập nhật (Update)
protected void btnUpdate_Click( object sender, EventArgs e)
{
// lấy vể dòng được chọn trên ListBox
DataRow targetRow =
dataTable.Rows[lbCustomers.SelectedIndex];
// thông báo cho người biết dòng cập nhật
lblMessage.Text = "Updating " + targetRow["CompanyName"];
Application.DoEvents( );
// hiệu chỉnh dòng
targetRow.BeginEdit( );
targetRow["CompanyName"] = txtCustomerName.Text;
targetRow.EndEdit( );
// lấy về___ các dòng thay đổi
DataSet DataSetChanged =
DataSet.GetChanges(DataRowState.Modified);
// đảm bảo không có dòng nào có lỗi
bool okayFlag = true;
if (DataSetChanged.HasErrors)
{
okayFlag = false;
string msg = "Error in row with customer ID ";



                     Bài tập thực hành Chuyên đề Visual Studio .NET   136
// kiểm tra lỗi trên từng bảng
foreach (DataTable theTable in DataSetChanged.Tables)
{
// nếu bảng có lỗi thì tìm lỗi trên dòng cụ thể
if (theTable.HasErrors)
{
// lấy các dòng có lỗi
DataRow[] errorRows = theTable.GetErrors( );
// duyệt qua từng dòng có lỗi để thống báo.
foreach (DataRow theRow in errorRows)
{
msg = msg + theRow["CustomerID"];
}
}
}
lblMessage.Text = msg;
}
// nếu không có lỗi
if (okayFlag)
{
// trộn các thay đổi trong 2 DataSet thành một
DataSet.Merge(DataSetChanged);
// cập nhật cơ sở dữ liệu
DataAdapter.Update(DataSet,"Customers");
// thông báo câu truy vấn cho người dùng
lblMessage.Text = DataAdapter.UpdateCommand.CommandText;
Application.DoEvents( );
// cập nhật DataSet và
// hiển thị dữ liệu mới cho ListBox
DataSet.AcceptChanges( );



                  Bài tập thực hành Chuyên đề Visual Studio .NET   137
PopulateLB( );
}
else // nếu có lỗi
DataSet.RejectChanges( );
}
// quản lý sự kiện xóa
protected void btnDelete_Click( object sender, EventArgs e)
{
// lấy về___ dòng được chọn trên ListBox
DataRow targetRow =
dataTable.Rows[lbCustomers.SelectedIndex];
// chuẩn bị thông báo cho người dùng
string msg = targetRow["CompanyName"] + " deleted. ";
// xóa dòng được chọn
dataTable.Rows[lbCustomers.SelectedIndex].Delete( );
// cập nhật thay đổi cho DataSet
DataSet.AcceptChanges( );
// cập nhật cơ sở dữ liệu
DataAdapter.Update(DataSet,"Customers");
// hiển thị lại ListBox với dữ liệu thay đổi
PopulateLB( );
// thông báo cho người dùng biết
lblMessage.Text = msg;
Application.DoEvents( );
}
public static void Main(string[] args)
{
Application.Run(new ADOForm1( ));
}
}



                     Bài tập thực hành Chuyên đề Visual Studio .NET   138
}
                                     PHẦN 4
                      XÂY DỰNG ỨNG DUNG WEB VỚI WEBFORMS
                             NG NG     ̣ NG


Công nghệ .NET được dùng để xây dựng các ứng dụng Web là ASP.NET, nó cung cấp
hai vùng tên khá mạnh và đầy đủ phục vụ cho việc tạo các ứng dụng Web là
System.Web và System.Web.UI. Trong phần này chúng ta sẽ tập trung chủ yếu vào
việc dùng ngôn ngữ C# để lập trình với ASP.NET.
Bộ công cụ Web Form cũng được thiết kế để hỗ trợ mô hình phát triển nhanh (RAD).
Với Web Form, ta có thể kéo thả các điều khiển trên Form thiết kế cũng như có thể
viết mã trực tiếp trong tập tin .aspx hay .aspx.cs. Ứng dụng Web sẽ được triển khai
trên máy chủ, còn người dùng sẽ tương tác với ứng dụng thông qua trình duyệt. .NET
còn hỗ trợ ta bộ cung cụ để tạo ra các ứng dụng tuân theo mô hình n - lớp (tầng - n
tier), giúp ta có thể quản lý được ứng dụng được dễ dàng hơn và nhờ thế nâng cao hiệu
suất phát triển phần mềm.
4.1 Tìm hiểu về Web Forms
Web Form là bộ công cụ cho phép thực thi các ứng dụng mà các trang Web do nó tạo
động ra được phân phối đến trình duyệt thông qua mạng Internet. Với Web Forms, ta
tạo ra các trang HTML với nội dung tĩnh và dùng mã C# chạy trên Server để xử lý dữ
liệu tĩnh này rồi tạo ra trang Web động, gửi trang này về trình duyệt dưới mã HTML
chuẩn. Web Forms được thiết để chạy trên bất kỳ trình duyệt nào, trang HTML gửi về
sẽ được gọt giũa sao cho thích hợp với phiên bản của trình duyệt. Ngoài dùng C#, ta
cũng có thể dùng ngôn ngữ VB.NET để tạo ra các ứng dụng Web tương tự. Web
Forms chia giao diện người dùng thành hai phần: phần thấy trực quan ( hay UI ) và
phần trang mã phía sau của UI. Quan điểm này thì tương tự với Windows Form,
nhưng với Web Forms, hai phần này nằm trên hai tập tin riêng biệt. Phần giao diện UI
được lưu trữ trong tập tin có phần mở rộng là .aspx, còn mã được lưu trữ trong tập tin
có phần mở rộng là .aspx.cs. Với môi trường làm việc được cung cấp bởi bộ Visual
Studio .NET, tạo các ứng dụng Web đơn giản chỉ là mở Form mới, kéo thả và viết mả
quản lý sự kiện thích hợp. Web Forms được tích hợp thêm một loạt các điều khiển
thực thi trên Server, có thể tự kiểm tra sự hợp lệ của dữ liệu ngay trên máy khách mà
ta không phải viết mã mô tả gì cà.
4.2 Các sự kiện của Web Forms
Một sự kiện (Events) được tạo ra khi người dùng nhấn chọn một Button, chọn một
mục trong ListBox hay thực hiện một thao tác nào đó trên UI. Các sự kiện cũng có thể
được phát sinh hệ thống bắt đầu hay kết thúc. Phương thức đáp ứng sự kiện gọi là trình
quản lý sự kiện, các trình quản lý sự kiện này được viết bằng mã C# trong trang mã
(code-behind) và kết hợp với các thuộc tính của các điều khiển thuộc trang.


                 Bài tập thực hành Chuyên đề Visual Studio .NET   139
Trình quản lý sự kiện là một “Delegate”, phương thức này sẽ trả về kiểu void, và có
hai đối số. Đối số đầu tiên là thể hiện của đối tượng phát sinh ra sự kiện, đối số thứ hai
là đối tượng EventArg hay một đối tượng khác được dẫn xuất từ đối tượng EventArgs.
Các sự kiện này được quản lý trên Server.
4.2.1 Sự kiện PostBack và Non-PostBack
PostBack là sự kiện sẽ khiến Form được gửi về Server ngay lập tức, chẳng hạn sự kiện
đệ trình một Form với phương thức Post. Đối lập với PostBack là Non- PostBack, sự
kiện này không gửi Form nên Server mà nó lưu sự kiện trên vùng nhớ Cache cho tới
khi có một sự kiện PostBack nữa xảy ra. Khi một điều khiển có thuộc tính
AutoPostBack là true thì sự kiện PostBack sẽ có tác dụng trên điều khiển đó:mặc
nhiên thuộc tính AutoPostBach của điều khiển DropDownList là false,ta phải đặt lại là
true thì sự kiện chọn một mục khác trong DropDownList này mới có tác dụng.
4.2.2 Trạng thái của ứng dụng Web (State)
Trạng thái của ứng dụng Web là giá trị hiện hành của các điều khiển và mọi biến trong
phiên làm việc hiện hành của người dùng. Web là môi trường không trạng thái, nghĩa
là mỗi sự kiện Post lên Server đều làm mất đi mọi thông tin về phiên làm việc trước
đó. Tuy nhiên ASP.NET đã cung cấp cơ chế hỗ trợ việc duy trì trạng thái về phiên của
người dùng. Bất kỳ trang nào khi được gửi lên máy chủ Server đều được máy chủ tổng
hợp thông tin và tái tạo lại sau đó mới gửi xuống trình duyệt cho máy khách.
ASP.NET cung cấp một cơ chế giúp duy trì trạng thái của các điều khiển phía máy chủ
(Server Control ) một cách tự động. Vì thế nếu ta cung cấp cho người dùng một danh
sách dữ liệu ListBox, và người dùng thực hiện việc chọn lựa trên ListBox này, sự kiện
chọn lựa này sẽ vẫn được duy trì sau khi trang được gửi lên máy chủ và gửi về cho
trình duyệt cho máy khách.
4.2.3 Chu trình sống của một Web-Form
Khi có yêu cầu một trang Web trên máy chủ Web sẽ tạo ra một chuỗi các sự kiện ở
máy chủ đó, từ lúc bắt đầu cho đến lúc kết thúc một yêu cầu sẽ hình thành một chu
trình sống ( Life-Cycle ) cho trang Web và các thành phần thuộc nó. Khi một trang
Web được yêu cầu, máy chủ sẽ tiến hành mở ( Load ) nó và khi hoàn tất yêu cầu máy
chủ sẽ đóng trang này lại, kết xuất của yêu cầu này là một trang HTML tương ứng sẽ
được gửi về cho trình duyệt. Dưới đây sẽ liệt kê một số sự kiện, ta có thể bắt các sự
kiện để xử lý thích hợp hay bỏ qua để ASP.NET xử lý mặc định.
Khởi tạo (Initialize) Là sự kiện đầu tiên trong chu trình sống của trang, ta có thể khởi
bất kỳ các thông số cho trang hay các điều khiển thuộc trang.
Mở trạng thái vùng quan sát (Load View State) Được gọi khi thuộc tính
ViewState của điều khiển được công bố hay gọ. Các giá trị trong ViewState sẽ được
lưu trữ trong một biến ẩn ( Hidden Field ), ta có thể lấy giá trị này thông qua hàm



                  Bài tập thực hành Chuyên đề Visual Studio .NET    140
LoadViewState() hay lấy trực tiếp. Kết thúc (Dispose) Ta có thể dùng sự kiện này để
giải phóng bất kỳ tài nguyên nguyên nào: bộ nhớ hay hủy bỏ các kết nối đến cơ sở dữ
liệu.
Ví dụ: Hiển thị chuỗi lên trang
Đầu tiên ta cần chạy Visual Studio .NET, sau đó tạo một dự án mới kiểu
WebApplication, ngôn ngữ chọn là C# và ứng dụng sẽ có tên là
ProgrammingCSharpWeb.Url mặc nhiên của ứng dụng sẽ có tên là http://localhost/
ProgrammingCSharpWeb. Visual Studio .NET sẽ đặt hầu hết các tập tin nó tạo ra cho
ứng dụng trong thư mụcWeb mặc định trên máy người dùng, ví dụ:
D:InetpubwwwrootProgrammingCSharpWeb. Trong .NET, một giải pháp
(Solution) có một hay hiều dự án (Project), mỗi dự án sẽ tạo ra một thư viện liên kết
động (DLL) hay tập tin thực thi (EXE). Để có thể chạy được ứng dụng Web Form, ta
cần phải cài đặt IIS và FrontPage Server Extension trên máy tính.
Khi ứng dụng Web Form được tạo, .NET tạo sẵn một số tập tin và một trang Web có
tên mặc định là WebForm1.aspx chỉ chứa mã HTML và WebForm1.cs chứa mã quản
lý trang. Trang mã .cs không nằm trong cửa sổ Solution Explorer, để hiển thị nó ta
chọn ProjectShow All Files, ta chỉ cần nhấn đúp chuột trái trên trang Web là cửa sổ
soạn thảo mã (Editor) sẽ hiện nên, cho phép ta viết mã quản lý trang. Để chuyển từ cửa
số thiết kế kéo thả sang cửa sổ mã HTML của trang, ta chọn hai Tab ở góc bên trái
phía dưới màn hình.
Đặt tên lại cho trang Web bằng cách nhấn chuột phải lên trang và chọn mục Rename
để đổi tên trang thành HelloWeb.aspx, .NET cũng sẽ tự động đổi tên trang mã của
trang thành HelloWeb.cs. NET cũng tạo ra một số mã HTML cho trang:
.NET đã phát sinh ra một số mã ASP.NET:
<%@ Page language="c#"
Codebehind="HelloWeb.cs"
AutoEventWireup="false"
Inherits="ProgrammingCSharpWeb.WebForm1" %>
Thuộc tính language chỉ ra ngôn ngữ lập trình được dùng trong trang mã để quản lý
trang, ở đây là C#. Codebehide xác định trang mã quản lý có tên HelloWeb.cs và
thuộc tính Inherits chỉ trang Web được thừa kế từ lớp WebForm1 được viết trong
HelloWeb.cs:
public class WebForm1: System.Web.UI.Page
Ta thấy trang này được thừa kế từ lớp System.Web.UI.Page, lớp này do ASP.NET
cung cấp, xác định các thuộc tính, phương thức và các sự kiện chung cho các trang
phía máy chủ. Mã HTML phát sinh định dạng thuộc tính của Form:



                 Bài tập thực hành Chuyên đề Visual Studio .NET   141
<form id="Form1" method="post" runat="server">
Thuộc tính id làm định danh cho Form, thuộc tính method có giá trị là “POST” nghĩa
là Form sẽ được gởi lên máy chủ ngay lập tức khi nhận một sự kiện do người dùng
phát ra ( như sự kiện nhấn nút ) và cờ IsPostBack trên máy chủ khi đó sẽ có giá trị là
true. Biến cờ này có giá trị là false nếu Form được đệ trình với phương thức “GET”
hay lần đầu tiên trang được gọi. Bất kỳ điều khiển nào hay Form có thuộc tính
runat=”server” thì điều khiển hay Form này sẽ được xử lý bởi ASP.NET Framework
trên máy chủ. Thuộc tính MS_POSITIONING =“GridLayout” trong thẻ <Body>, cho
biết cách bố trí các điều khiển trên Form theodạng lưới, ngoài ra ta còn có thể bố trí
các điều khiển trôi lổi trên trang, bằng cáchgán thuộc tính MS_POSITIONING thành
“FlowLayout”.
Hiện giờ Form của ta là trống, để hiển thị một chuỗi gì đó lên màn hình, ta gõ dòng mã
sau trong thẻ <body>:
Hello World! It is now <% = DateTime.Now.ToString( ) %>
Giống với ASP, phần nằm trong dấu <% %> được xem như là mã quản lý cho trang,
ở đây là mã C#. Dấu = chỉ ra một giá trị nhận được từ một biến hay một đối tượng nào
đó, ta cũng có thể viết mã trên lại như sau với cùng chức năng:
Hello World! It is now
<% Response.Write(DateTime.Now.ToString( )); %>
Thực thi trang này ( Ctrl-F5 ), kết quả sẽ hiện trên trình duyệt như sau:
Để thêm các điều khiển cho trang, hoặc là ta có thể viết mã trong của sổ HTML hoặc
là kéo thả các điều khiển trên bộ công của Web Form vào cửa sổ thiết kế trang.
ASP.NET sẽ tự động phát sinh ra kết quả từ mã HTML thành các điều khiển cũng như
từ các điều khiển trên trang thiết thành mã HTML tương ứng. Ví dụ, kéo hai
RadioButton vào trang và gán cùng một giá trị nào đó cho thuộc tính GroupName của
cả hai điều khiển, thuộc tính này sẽ làm cho các nút chọn loại trừ lẫn nhau. Mã HTML
của trang trong thẻ <Form> do ASP.NET phát sinh sẽ như sau:
Các điều khiển của ASP.NET, có thêm chữ “asp:” phía trước tên của điều khiển đó,
được thiết kế mang tính hướng đối tượng nhiều hơn.
<asp:RadioButton>
<asp:CheckBox>
<asp:Button>
<asp:TextBox rows="1">
<asp:TextBox rows="5">




                  Bài tập thực hành Chuyên đề Visual Studio .NET    142
Ngoài các điều khiển của ASP.NET, các điều khiển HTML chuẩn cũng được
ASP.NET hỗ trợ. Tuy nhiên các điều khiển không tạo sự dễ đọc trong mã nguồn do
tính đối tượng trên chúng không rõ ràng, các điều khiển HTML chuẩn ứng với năm
điều khiển trên là:
<input type = "radio">
<input type="checkbox">
<input type="button">
<input type="text">
<textarea>
4.3 Điều khiển xác nhận hợp
ASP.NET cung cấp một tập các điều khiển xác nhận hợp lệ dữ liệu nhập phía máychủ
cũng như ở dưới trình duyệt của máy khách. Tuy nhiên việc xác nhận hợp lệ dưới máy
khách chỉ là một chọn lựa, ta có thể tắt nó đi, nhưng việc xác nhận hợp lệ trên máy chủ
thông qua các điều khiển này là bắt buộc, nhằm phòng ngừa một số trường hợp dữ liệu
nhập là giả mạo. Việc kiểm tra hợp lệ của mã trên máy chủ là đề phòng các trường
hợp. Một số loại xác nhận hợp lệ: dữ liệu không được rỗng, thỏa một định dạng dữ liệu
nào đó …
Các điều khiển xác nhận hợp lệ phải được gắn liền với một điều khiển nhận dữ
liệuHTML nào đó, các điều khiển nhập được liệt trong bảng sau:
Ứng với một điều khiển nhập HTML, ta có thể gắn nhiều điều khiển xác nhận hợplệ
cho nó, bảng dưới đây sẽ liệt kê các điều khiển nhập hiện có:
CompareValidator So sánh các giá trị của hai điều khiển để xem có bằng nhau hay
không
CustomValidator Gọi một hàm do người dùng định nghĩa để thi hành việc kiểm tra
RangeValidator Kiểm tra xem một mục có nằm trong một miền đã cho hay không
RegularExpressionvalidator Kiểm tra người dùng có sửa đổi một mục ( mà giá trị của
nó khác
với một giá trị đã chỉ định ban đầu, ngầm định giá trị ban đầu là
một chuỗi trống ) hay không
ValidationSummary Thông báo sự hợp lệ trên các điều khiển
4.4 Một số ví dụ mẫu minh họa
Một cách thuận tiện nhất để học một công nghệ mới chính là dựa vào các ví dụ, vìvậy
trong phần này chúng ta sẽ khảo sát một vài ví dụ để minh họa cho phần lý thuyết của
chúng ta. Như ta đã biết, ta có thể viết mã quản lý theo hai cách: hoặc là viết trong tập



                  Bài tập thực hành Chuyên đề Visual Studio .NET    143
tin .cs hoặc là viết trực tiếp trong trang chứa mã HTML. Ở đây để dễ tập trung vào các
ví dụ của chúng ta, ta sẽ viết mã quản lý trực tiếp trên trang HTML.
Kết buộc dữ liệu
Không thông qua thuộc tính DataSource
Ứng dụng của chúng ta đơn giản chỉ hiện lên trang tên khách hàng và số hóa đơn bằng
cách dùng hàm DataBind(). Hàm này sẽ kết buộc dữ liệu của mọi thuộc tính hay của
bất kỳ đối tượng.
<html>
<head>
// mã quản lý C# sẽ___ được viết trong thẻ <script> này
<script language="C#" runat="server">
// trang sẽ___ gọi hàm này đầu tiên, ta sẽ___ thực hiện kết buộc
// trực tiếp trong hàm này
void Page_Load(Object sender, EventArgs e) {
Page.DataBind();
}
// lấy giá trị của thuộc tính thông qua thuộc tính // get
string custID{
get {
return "ALFKI";
}
}
int orderCount{
get {
return 11;
}
}
</script>
</head>
<body>
<h3><font face="Verdana"> Ket buoc khong dung DataSource



                   Bài tập thực hành Chuyên đề Visual Studio .NET   144
</font></h3>
<form runat=server>
Khach hang: <b><%# custID %></b><br>
So hoa don: <b><%# orderCount %></b>
</form>
</body>
</html>
Điều khiển DataList với DataSource
Trong ví dụ này, ta sẽ dùng thuộc tính DataSource của điều khiển <asp:DataList> để
kết buộc dữ liệu, ta sẽ cung cấp cho thuộc tính DataSource này một bảng dữ liệu giả,
sau đó dùng hàm DataBinder.Eval()để kết buộc dữ liệu trong DataSource theo một
định dạng ( Format ) thích hợp mong muốn. Dữ liệu sẽ được hiển thị lên màn hình
dưới dạng một bảng các hóa đơn sau khi ta gọi hàm DataBind().
//Không gian tên chứa các đối tượng của ADO.NET
<%@ Import namespace="System.Data" %>
<html>
<head>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs e) {
// nếu trang được gọi lần đầu tiên
if (!Page.IsPostBack) {
// tạo ra một bảng dữ liệu mới gồm 4 cột , sau đó thêm dữ
// liệu giả cho bảng
DataTable dt = new DataTable();
DataRow dr;
// thêm 4 cột DataColumn vào bảng, mỗi cột có các
// kiểu dữ liệu riêng
dt.Columns.Add(new DataColumn("IntegerValue", typeof(Int32)));
dt.Columns.Add(new DataColumn("StringValue", typeof(string)));
dt.Columns.Add(new DataColumn("DateTimeValue", typeof(DateTime)));
dt.Columns.Add(new DataColumn("BoolValue", typeof(bool)));



                  Bài tập thực hành Chuyên đề Visual Studio .NET   145
// thêm 9 dòng dữ liệu cho bảng bằng cách tạo ra
// một dòng mới dùng phương thức NewRow() của đối
// tượng DataTable, sau đó gán dữ liệu giả cho
// dòng này và thêm dòng dữ liệu này vào bảng
for (int i = 0; i < 9; i++) {
dr = dt.NewRow();
dr[0] = i;
dr[1] = "Item " + i.ToString();
dr[2] = DateTime.Now;
dr[3] = (i % 2 != 0) ? true: false;
dt.Rows.Add(dr);
}
// gán bảng dữ liệu cho thuộc tính DataSource của điều
// khiển DataList, sau đó thực hiện kết buộc bằng hàm
// DataBind()
dataList1.DataSource = new DataView(dt);
dataList1.DataBind();
}
}
</script>
</head>
<body>
<h3><font face="Verdana">Ket buoc du lieu dung DataSource thong qua
ham DataBind.Eval() </font></h3>
<form runat=server>
// điều khiển danh sách cho phép ta kết buộc dữ liệu khá
// linh động, ta chỉ cần cung cấp cho nó một DataSource
// thích hợp, sau đó gọi hàm DataBind()để hiển thị dữ liệu // lên
trang
<asp:DataList id="dataList1" runat="server"



                    Bài tập thực hành Chuyên đề Visual Studio .NET   146
RepeatColumns="3"
Width="80%"
BorderColor="black"
BorderWidth="1"
GridLines="Both"
CellPadding="4"
CellSpacing="0">
// đây là một thuộc tính của lưới, khi gọi hàm
// DabaBind(), dữ liệu trong DataSource sẽ___ được trích ra
// (nếu là danh các đối tượng thì mỗi lần trích sẽ___ lấy ra
// một phần tử kiểu đối tượng đó, sau đó dùng hàm
// DataBinder.Eval()để gán giá trị, còn nếu là một bảng
// dữ liệu thì mỗi lần kết buộc sẽ___ lấy ra một dòng dữ
// liệu, hàm DataBind.Eval() sẽ___ lấy dữ liệu của từng
// trường) để kết buộc lên trang. Nó sẽ___ lặp lại thao tác
// này cho tới khi dữ liệu được kết buộc hết.
<ItemTemplate>
//lấy dữ liệu trên cột đầu tiên để kết buộc
Ngay hoa don: <%# DataBinder.Eval(Container.DataItem,
"DateTimeValue", "{0:d}") %>
//lấy dữ liệu trên cốt thứ 2
So luong: <%# DataBinder.Eval(Container.DataItem, "IntegerValue",
"{0:N2}") %>
//cột thứ 3
Muc: <%# DataBinder.Eval(Container.DataItem, "StringValue") %>
//cột thứ 4
Ngay hoa don: <asp:CheckBox id=chk1 Checked='<%#
(bool)DataBinder.Eval(Container.DataItem, "BoolValue") %>'
runat=server/><p>
</ItemTemplate>



                   Bài tập thực hành Chuyên đề Visual Studio .NET   147
</asp:Datalist>
</form>
</body>
</html>
Kết buộc với điều khiển DataGrid
Trong ví trước, ta đã tìm hiểu sơ qua về cách đẩy dữ liệu vào thuộc tính DataSource
của điều khiển DataList thông qua hàm kết buộc DataBind().Ví dụ này chúng ta sẽ
khảo sát thêm về cách kết buộc trên điều khiển lưới DataGrid và cách dùng điều khiển
xác nhận hợp lệ trên dữ liệu. Khi ứng dụng chạy sẽ hiển thị một bảng dữ liệu lên trang,
người dùng có thể hiệu chỉnh bất kỳ một dòng nào trên bảng dữ liệu bằng cách nhấn
vào chuỗi lệnh hiệu chỉnh ( Edit ) trên lưới, gõ vào các dữ liệu cần hiệu chỉnh, khi
muốn hủy bỏ thao tác hiệu chỉnh ta nhấn chọn chuỗi bỏ qua (Cancel). Để tập trung vào
mục đích của ví dụ, chúng ta sẽ dùng bảng dữ liệu giả, cách làm sẽ tương tự trên bảng
dữ liệu lấy ra từ cơ sở dữ liệu. Sau đây là mã của ví dụ:
//không gian tên cần thiết để truy cập đến các đối tương ADO.NET
<%@ Import Namespace="System.Data" %>
<html>
<script language="C#" runat="server">
//khai báo đối tượng bảng và khung nhìn
DataTable Cart;
DataView CartView;
// lấy dữ liệu trong Session, nếu không có thì ta sẽ___ tạo ra một
// bảng dữ liệu khác
void Page_Load(Object sender, EventArgs e) {
if (Session["DG6_ShoppingCart"] == null) {
Cart = new DataTable();
//bảng sẽ___ có 3 cột đều có kiểu là chuỗi
Cart.Columns.Add(new DataColumn("Qty", typeof(string)));
Cart.Columns.Add(new DataColumn("Item", typeof(string)));
Cart.Columns.Add(new DataColumn("Price", typeof(string)));
//đẩy định danh của bảng vào phiên làm việc hiện thời
Session["DG6_ShoppingCart"] = Cart;



                  Bài tập thực hành Chuyên đề Visual Studio .NET     148
// tạo dữ liệu mẫu cho bảng
for (int i=1; i<5; i++) {
DataRow dr = Cart.NewRow();
dr[0] = ((int)(i%2)+1).ToString();
dr[1] = "Item " + i.ToString();
dr[2] = ((double)(1.23 * (i+1))).ToString();
Cart.Rows.Add(dr);
}
}
else {
//nếu bảng đã có sẵn trong Session, ta sẽ___ lấy ra dùng
Cart = (DataTable)Session["DG6_ShoppingCart"];
}
// tạo ra khung nhìn cho bảng, sau đó sắp xếp khung nhìn theo // cột
Item
CartView = new DataView(Cart);
CartView.Sort = "Item";
// nếu trang được gọi lần đầu tiên thì kết buộc dữ liệu thông // qua
hàm BindGrid()của ta
if (!IsPostBack) {
BindGrid();
}
}
// sự kiện nhấn chuỗi hiệu chỉnh (Edit) trên lưới, ta sẽ___ lấy chỉ //
mục của dòng cần hiệu chỉnh thông qua đối tượng
// DataGridCommandEventArgs, sau đó truyền chỉ mục này cho điều //
khiển lưới của ta và gọi hàm kết buộc của ta để đẩy dữ liệu
// lên lưới
public void MyDataGrid_Edit(Object sender, DataGridCommandEventArgs
e) {



                   Bài tập thực hành Chuyên đề Visual Studio .NET      149
MyDataGrid.EditItemIndex = (int)e.Item.ItemIndex;
BindGrid();
}
//sự kiện nhấn bỏ qua trên lưới (Cancel)
public void MyDataGrid_Cancel(Object sender,
DataGridCommandEventArgs e) {
MyDataGrid.EditItemIndex = -1;
BindGrid();
}
//sau khi hiệu chỉnh dữ liệu, người dùng tiến hành cập nhật public
void MyDataGrid_Update(Object sender, DataGridCommandEventArgs e) {
// lấy dữ liệu trên TextBox
string item = e.Item.Cells[1].Text;
string qty = ((TextBox)e.Item.Cells[2].Controls[0]).Text;
string price = ((TextBox)e.Item.Cells[3].Controls[0]).Text;
// Ở đây, do chúng ta dùng dữ liệu giả lưu trên bộ nhớ chính, // nếu
dùng cơ sở dữ liệu thì chúng ta sẽ___ tiến hành hiệu chỉnh // trực tiếp
trong cơ sở dữ liệu bằng các câu truy vấn:
// UPDATE, SELECT, DELETE
//xóa dòng cũ
CartView.RowFilter = "Item='"+item+"'";
if (CartView.Count > 0) {
CartView.Delete(0);
}
CartView.RowFilter = "";
//tạo dòng mới và thêm vào bảng
DataRow dr = Cart.NewRow();
dr[0] = qty;
dr[1] = item;
dr[2] = price;



                  Bài tập thực hành Chuyên đề Visual Studio .NET     150
Cart.Rows.Add(dr);
MyDataGrid.EditItemIndex = -1;
BindGrid();
}
//kết buộc dữ liệu thông qua thuộc tính DataSource của lưới
public void BindGrid() {
MyDataGrid.DataSource = CartView;
MyDataGrid.DataBind();
}
</script>
<body style="font: 10pt verdana">
<form runat="server">
<h3><font face="Verdana">Using an Edit Command Column in
DataGrid</font></h3>
//Khai báo các thông số cho lưới, các sự kiện trên lưới:
OnEditCommand: khi người dùng nhấn chuỗi hiệu chỉnh (Edit)
OnCancelCommand: nhấn chuỗi bỏ qua hiệu chỉnh (Cancel)
OnUpdateCommand: nhấn chuỗi cập nhật hiệu chỉnh (Update)
<asp:DataGrid id="MyDataGrid" runat="server"
BorderColor="black"
BorderWidth="1"
CellPadding="3"
Font-Name="Verdana"
Font-Size="8pt"
HeaderStyle-BackColor="#aaaadd"
OnEditCommand="MyDataGrid_Edit"
OnCancelCommand="MyDataGrid_Cancel"
OnUpdateCommand="MyDataGrid_Update"
AutoGenerateColumns="false"
>



                  Bài tập thực hành Chuyên đề Visual Studio .NET   151
// các thông số hiệu chỉnh trên cột, ở đây ta chỉ cho người
// dùng hiệu chỉnh trên cột số lượng và giá hóa đơn
<Columns>
<asp:EditCommandColumn
EditText="Edit"
CancelText="Cancel"
UpdateText="Update"
ItemStyle-Wrap="false"
HeaderText="Edit Command Column"
HeaderStyle-Wrap="false"
/>
<asp:BoundColumn HeaderText="Item" ReadOnly="true"
DataField="Item"/>
<asp:BoundColumn HeaderText="Quantity" DataField="Qty"/>
<asp:BoundColumn HeaderText="Price" DataField="Price"/>
</Columns>
</asp:DataGrid>
</form>
</body>
</html>
Điều khiển xác nhận hợp lệ
Việc xác nhận hợp lệ là cần thiết với các ứng dụng cần yêu cầu nhập liệu, việc đưa ra
các điều khiển có khả năng xác nhận hợp lệ trực tiếp dưới máy khách lẫn ở trên máy
chủ, đây có thể là một tính năng mới của ASP.NET, ta không cần phải viết mã kiểm
tra gì cả, mã kiểm tra dưới trình duyệt ( chẳng hạn như Java Script ) sẽ được ASP.NET
tự động phát sinh. Để gắn một điều khiển bắt lỗi vào một điều khiển cần bắt lỗi ta chỉ
cần gán thuộc tính ControlToValidate của điều khiển bắt lỗi bằng giá trị định danh id
của điều khiển cần bắt lỗi, ví dụ: Để bắt lỗi điều khiển TextBox không được trống, ta
viết má như sau:
//điều khiển cần bắt lỗi
<ASP:TextBox id=TextBox1 runat=server />
//điều khiển bắt lỗi hộp nhập liệu TextBox1



                  Bài tập thực hành Chuyên đề Visual Studio .NET   152
<asp:RequiredFieldValidator id="RequiredFieldValidator2"
ControlToValidate="TextBox1"
ErrorMessage="Card Number. "
Display="Static"
Width="100%" runat=server>
*
</asp:RequiredFieldValidator>
Ví dụ của chúng ta sẽ cho hiển thị 2 hộp thoại DropDownList, 2 nút chọn RadioButton
và một hộp thoại nhập TextBox, nếu tồn tại mục nhập nào trống khi nhấn nút xác nhận
Validate, thì các điều khiển xác nhận hợp lệ sẽ hiển thị lỗi tương ứng. Thông điệp lỗi
có thể được hiển thị theo ba cách khác nhau: liệt kê theo danh sách (List), liệt kê trên
cùng một dòng ( Single Paragraph ), liệt kê danh sách với dấu chấm tròn ở đầu ( Bullet
List ). Mã hoàn chỉnh của ví dụ được liệt kê như sau:
// không cho phép điều khiển xác nhận hợp lệ dưới máy khách bằng
// cách gán thuộc tính clienttarget = downlevel
<%@ Page clienttarget=downlevel %>
<html>
<head>
<script language="C#" runat=server>
// thay đổi chế___ độ hiển thị lỗi bằng cách chọn 1 trong 3 mục
// trong hộp thoại ListBox
void ListFormat_SelectedIndexChanged(Object Sender, EventArgs E )
{
valSum.DisplayMode = (ValidationSummaryDisplayMode)
ListFormat.SelectedIndex;
}
</script>
</head>
<body>
<h3><font face="Verdana">Ví dụ về___ xác nhận điều khiển hợp lệ
ValidationSummary</font></h3>
<p>


                 Bài tập thực hành Chuyên đề Visual Studio .NET   153
<form runat="server">
<table cellpadding=10><tr> <td>
<table bgcolor="#eeeeee" cellpadding=10><tr><td colspan=3>
<font face=Verdana size=2><b>Credit Card
Information</b></font></td></tr>
<tr>
<td align=right>
<font face=Verdana size=2>Card Type:</font></td>
<td>
// danh sách các nút chọn được bắt lỗi bởi điều //khiển xác nhận hợp
lệ RequireFieldValidator1
<ASP:RadioButtonList id=RadioButtonList1 RepeatLayout="Flow"
runat=server>
<asp:ListItem>MasterCard</asp:ListItem>
<asp:ListItem>Visa</asp:ListItem>
</ASP:RadioButtonList>
</td>
//điều khiển xác nhận hợp lệ cho các nút chọn //RadioButtonList1
<td align=middle rowspan=1>
<asp:RequiredFieldValidator id="RequiredFieldValidator1"
ControlToValidate="RadioButtonList1"
ErrorMessage="Card Type. "
Display="Static"
InitialValue="" Width="100%" runat=server>
*
</asp:RequiredFieldValidator>
</td></tr>
<tr>
<td align=right>
<font face=Verdana size=2>Card Number:</font>



                   Bài tập thực hành Chuyên đề Visual Studio .NET   154
</td>
<td>
<ASP:TextBox id=TextBox1 runat=server />
</td>
<td>
//điều khiển xác nhận hợp lệ trên hộp thoại //nhập liệu TextBox, nếu
chuỗi là trống khi //nhấn nút Validate thì sẽ___ bị bắt lỗi.
<asp:RequiredFieldValidator id="RequiredFieldValidator2"
ControlToValidate="TextBox1"
ErrorMessage="Card Number. "
Display="Static"
Width="100%" runat=server>
*
</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td align=right>
<font face=Verdana size=2>Expiration Date:</font>
</td>
<td>
//hộp thoại DropDownList dùng để hiển thị //danh sách các ngày, nếu
người dùng chọn //mục trống trong DropDownList này thì sẽ___ bị //điều
khiển xác nhận hợp lệ //RequireFieldValidator3 bắt lỗi
<ASP:DropDownList id=DropDownList1 runat=server>
<asp:ListItem></asp:ListItem>
<asp:ListItem >06/00</asp:ListItem>
<asp:ListItem >07/00</asp:ListItem>
<asp:ListItem >08/00</asp:ListItem>
<asp:ListItem >09/00</asp:ListItem>



                   Bài tập thực hành Chuyên đề Visual Studio .NET   155
<asp:ListItem >10/00</asp:ListItem>
<asp:ListItem >11/00</asp:ListItem>
<asp:ListItem >01/01</asp:ListItem>
<asp:ListItem >02/01</asp:ListItem>
<asp:ListItem >03/01</asp:ListItem>
<asp:ListItem >04/01</asp:ListItem>
<asp:ListItem >05/01</asp:ListItem>
<asp:ListItem >06/01</asp:ListItem>
<asp:ListItem >07/01</asp:ListItem>
<asp:ListItem >08/01</asp:ListItem>
<asp:ListItem >09/01</asp:ListItem>
<asp:ListItem >10/01</asp:ListItem>
<asp:ListItem >11/01</asp:ListItem>
<asp:ListItem >12/01</asp:ListItem>
</ASP:DropDownList>
</td>
<td>
//điều khiển xác nhận hợp lệ trên //DropDownList1 hiển thị ngày hết
hạn, nếu //người dùng chọn một mục trống trên //DropDownList thì
điều khiển này sẽ___ phát //sinh ra lỗi
<asp:RequiredFieldValidator id="RequiredFieldValidator3"
ControlToValidate="DropDownList1"
ErrorMessage="Expiration Date. "
Display="Static"
InitialValue=""
Width="100%"
runat=server>
*
</asp:RequiredFieldValidator></td>
</tr>



                  Bài tập thực hành Chuyên đề Visual Studio .NET   156
<tr>
<td>
//nút nhấn để xác định hợp lệ
<ASP:Button id=Button1 text="Validate" runat=server /></td></tr>
</table>
</td>
<td valign=top>
<table cellpadding=20><tr><td>
//điều khiển dùng để hiện thị các lỗi lên trang, //nó sẽ___ bắt bất kỳ
lỗi nào được phát sinh bởi các //điều khiển DropDownList để hiển thị
<asp:ValidationSummary ID="valSum" runat="server"
HeaderText="You must enter a value in the following fields:"
Font-Name="verdana"
Font-Size="12"
/>
</td></tr></table>
</td>
</tr>
</table>
<font face="verdana" size="-1">Select the type of validation summary
display you wish: </font>
//Danh sách liệt kê 3 cách hiển thị lỗi
<asp:DropDownList id="ListFormat" AutoPostBack=true
OnSelectedIndexChanged="ListFormat_SelectedIndexChanged"
runat=server >
<asp:ListItem>List</asp:ListItem>
<asp:ListItem selected>Bulleted List</asp:ListItem>
<asp:ListItem>Single Paragraph</asp:ListItem>
</asp:DropDownList>
</form>



                  Bài tập thực hành Chuyên đề Visual Studio .NET    157
</body>
</html>


4.5 Các dịch vụ Web
Hiện nay, vẫn còn một số hạn chế lớn trong các ứng dụng Web. Người dùng bị giới
hạn chỉ thực hiện được những nội dung đã được cấu trúc cho một trang cụ thể và xem
dữ liệu thông qua một số giao diện cụ thể nào đó đã được thiết kế trên máy chủ. Do đó
người dùng muốn lấy được thông tin được linh động và hiệu quả hơn. Hơn nữa, thay
vì ta hiển thị thông tin thông qua trình duyệt Web, ta muốn chạy một phần mềm trực
tiếp trên máy khách mà có thể trao đổi dữ liệu trên máy chủ tuỳ ý. Công nghệ .NET
cho phép xây dụng cách dịch vụ Web ( Web Services ) đáp ứng được các yêu cầu trên.
Ý tưởng chính là: thay vì liệt kê các thông tin theo dạng HTML, trang tạo sẵn một loạt
các lệnh gọi hàm. Các lệnh gọi hàm này có thể trao đổi thông tin qua lại giữa các hệ cơ
sở dữ liệu trên máy chủ. Các hàm này có thể chấp nhận các tham số và có thể trả về
một giá trị tùy ý.
Các dịch vụ Web vẫn dựa trên giao thức HTTP để truyền dữ liệu, đồng thời nó cần
phải sử dụng thêm một loại giao thức để phục vụ cho việc gọi hàm. Hiện nay có hai
giao thức được dùng chủ yếu là: SOAP ( Simple Object Access Protocol ) và SDL (
Service Description Language, đây là giao thức riêng của Microsoft ). Cả hai giao thức
này đều được xây dụng dựa trên XML, mục đích chung của chúng là giúp định nghĩa
các lệnh gọi hàm, tham số và giá trị.
Ngoài ra, Microsoft cũng đưa ra thêm một ý tưởng mới về tập tin Discovery File, có
phần mở rộng là .disco. Tập tin dạng này dùng để cung cấp các thông tin cho các trình
duyệt để các trình duyệt này có thể xác định được các trang trên các máy chủ mà có
chứa các dịch vụ Web.
Sau đây, ta sẽ tìm hiểu một ví dụ nhằm minh họa việc tạo ra một dịch vụ Web, đóng
vai trò là một thư viện chứa một tập các hàm tiện ích. Trang Web của chúng ta sẽ sử
dụng các hàm của dịch vụ này. Dịch vụ Web của chúng sẽ có tên MathService, đơn
giản là định nghĩa bốn phương thức cộng, trừ, nhân, chia trên hai số thực bất kỳ. Mỗi
phương thức đều nhận vào hai đối số kiểu số thực và trả về kết quả cũng có kiểu số
thực.
Đầu tiên ta cần tạo một dự án kiểu Web Service bằng cách chọn: New ProjectVisual
C# ProjectASP.NET Web Service và đặt tên cho dự án là MathService và đổi tên
dịch vụ thành MathService.asmx. NET có tạo sẵn cho chúng ta một số tập tin như:
• Service1.asmx: được trình duyệt yêu cầu, tương tự với tập tin .aspx.
• WebService1.cs: trang chứa mã C# quản lý.
• DiscoFile1.disco: tập tin khám phá.


                 Bài tập thực hành Chuyên đề Visual Studio .NET   158
Trong ví dụ này, chúng ta sẽ tạo ra một Web Form mới và thiết giao diện như sau:
Web Form sẽ gọi thực thi các hàm của dịch vụ Web.
Dự án của ta sẽ thừa kế namespace là System.Web.Services.WebService,nơi chứa các
thuộc tính và phương thức cần thiết để tạo dịch vụ Web.
public class MathService: System.Web.Services.WebService
Trên mỗi phương thức ta cần khai báo thuộc tính [WebMethod], để chỉ ra đây là
phương thức sẽ được sử dụng cho dịch vụ Web. Mã của tập tin dịch vụ sẽ như sau:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
namespace MathService
{
public class MathService:System.Web.Services.WebService
{
public MathService()
{
InitializeComponent();
}
#region Component Designer generated code
private IContainer components = null;
private void InitializeComponent()
{
}
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{


                 Bài tập thực hành Chuyên đề Visual Studio .NET   159
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
//4 hàm toán học của dịch vụ Web, trên mỗi phương thức
//ta cần khai báo thuộc tính [WebMethod] để chỉ đây là
//phương thức dành cho dịch vụ Web.
[WebMethod]
public float Add(float a, float b)
{
return a + b;
}
[WebMethod]
public float Subtract(float a, float b)
{
return a - b;
}
[WebMethod]
public float Multiply(float a, float b)
{
return a * b;
}
[WebMethod]
public float Divide(float a, float b)
{
if (b==0) return -1;
return a / b;
}
}



                   Bài tập thực hành Chuyên đề Visual Studio .NET   160
}
Bây giờ chúng ta sẽ viết mã thực thi cho trang Web. Trang Web của chúng ta sẽ gọi
các hàm của dịch vụ tương ứng với các phép cộng, trừ, nhân, chia . Sau đây là mã của
trang Web:
<%@ Import Namespace="MathService" %>
<html>
<script language="C#" runat="server">
float operand1 = 0;
float operand2 = 0;
public void Submit_Click(Object sender, EventArgs E)
{
try
{
operand1 = float.Parse(Operand1.Text);
operand2 = float.Parse(Operand2.Text);
}
catch (Exception) { /* bỏ qua lỗi nếu có */ }
Các dịch vụ Web Gvhd: Nguyễn Tấn Trần Minh Khang
195
//tạo ra một đối tượng dịch vụ MathService để có thể truy cập đến
//các hàm thành viên của chúng.
MathService service = new MathService();
switch (((Control)sender).ID)
{
case "Add": Result.Text = "<b>Result</b> = " +
service.Add(operand1, operand2).ToString(); break;
case "Subtract": Result.Text = "<b>Result</b> = " +
service.Subtract(operand1, operand2).ToString(); break;
case "Multiply": Result.Text = "<b>Result</b> = " +
service.Multiply(operand1, operand2).ToString(); break;
case "Divide": Result.Text = "<b>Result</b> = " +


                 Bài tập thực hành Chuyên đề Visual Studio .NET     161
service.Divide(operand1, operand2).ToString(); break;
}
}
</script>
<body style="font: 10pt verdana">
<h4>Using a Simple Math Service
</h4>
<form runat="server">
<div style="padding:15,15,15,15;backgroundcolor:
beige;width:300;border-color:black;borderwidth:
1;border-style:solid">
Operand 1:<br>
<asp:TextBox id="Operand1" Text="15" runat="server"
/><br>
Operand 2:<br>
<asp:TextBox id="Operand2" Text="5" runat="server"
/><p>
<input type="submit" id="Add" value="Add"
OnServerClick="Submit_Click" runat="server">
<input type="submit" id="Subtract" value="Subtract"
OnServerClick="Submit_Click" runat="server">
<input type="submit" id="Multiply" value="Multiply"
OnServerClick="Submit_Click" runat="server">
<input type="submit" id="Divide" value="Divide"
OnServerClick="Submit_Click" runat="server">
<p>
<asp:Label id="Result" runat="server" />
</div>
</form>
</body>



                 Bài tập thực hành Chuyên đề Visual Studio .NET   162
</html>




PHẦN                                                                                5
   PHỤ LỤC
Phụ lục A Chuỗi kết nối cho các loại nguồn dữ liệu

Phụ lục B Bảng tương quan/chuyển đổi kiểu dữ liệu ở .NET Framework với các
     Data Provider
.NET
Framework System.Data.DbType      SqlDbType           OleDbType       OdbcType
type
bool         Boolean                      Bit                    Boolean           Bit
byte         Byte                         TinyInt                UnsignedTinyInt   TinyInt
byte[]       Binary                       VarBinary. Việc VarBinary                Binary
                                          chuyển đổi ngầm
                                          định này là không
                                          đúng nếu mảng
                                          byte là lớn hơn
                                          kích thước tối đa
                                          của           một
                                          VarBinary (8000
                                          bytes).
char                                      Không hỗ trợ.          Char              Char
DateTime     DateTime                     DateTime               DBTimeStamp       DateTime
Decimal      Decimal                      Decimal                Decimal           Numeric
double       Double                       Float                  Double            Double
float        Single                       Real                   Single            Real
Guid         Guid                         UniqueIdentifier Guid                    UniqueIdentifi
Int16        Int16                        SmallInt               SmallInt          SmallInt
Int32        Int32                        Int                    Int               Int
Int64        Int64                        BigInt                 BigInt            BigInt
object       Object                       Variant                Variant           Không hỗ trợ.
string       String                       NVarChar.         VarWChar               NVarChar
                                          Chuyển đổi ngầm
                                          định này là không
                                          đúng nếu string
                                          lớn hơn kích


                Bài tập thực hành Chuyên đề Visual Studio .NET         163
.NET
Framework System.Data.DbType           SqlDbType              OleDbType        OdbcType
type
                                       thước tối đa của
                                       một NVarChar
                                       (4000 ký tự).
TimeSpan   Time                        Không hỗ trợ.          DBTime           Time
UInt16     UInt16                      Không hỗ trợ.          UnsignedSmallInt Int
UInt32     UInt32                      Không hỗ trợ.          UnsignedInt      BigInt
UInt64     UInt64                      Không hỗ trợ.          UnsignedBigInt   Numeric
           AnsiString                  VarChar                VarChar          VarChar
           AnsiStringFixedLength Char                         Char             Char
           Currency                    Money                  Currency         Không hỗ trợ.
           Date                        Không hỗ trợ.          DBDate           Date
           SByte                       Không hỗ trợ.          TinyInt          Không hỗ trợ.
           StringFixedLength           NChar                  WChar            NChar
           Time                        Không hỗ trợ.          DBTime           Time
           VarNumeric                  Không hỗ trợ.          VarNumeric       Không hỗ trợ.




             Bài tập thực hành Chuyên đề Visual Studio .NET       164
PHẦN
   TÀI LIỆU THAM KHẢO
  (1) Stephen C. Perry, Core C# and .NET, Prentice Hall PTR, 2005
  (2) Microsoft Corporation, MSDN 2005




                Bài tập thực hành Chuyên đề Visual Studio .NET   165

More Related Content

PDF
Cap nhat CSDL trong VB.NET
PPT
Transition to System Design
PPTX
Cursor & Function trong SQL Server
DOCX
Quản lý hoạt động giảng dạy sử dụng ASP.NET
PDF
Ccna tiếng việt full[bookbooming.com]
PDF
Giao trinh-solidwork[bookbooming.com]
PDF
Bcvt.đttx.nhđt.ngôn ngữ lập trình c++[bookbooming.com]
PDF
Giáo trình nhập môn tin học đỗ thị mơ[bookbooming.com]
Cap nhat CSDL trong VB.NET
Transition to System Design
Cursor & Function trong SQL Server
Quản lý hoạt động giảng dạy sử dụng ASP.NET
Ccna tiếng việt full[bookbooming.com]
Giao trinh-solidwork[bookbooming.com]
Bcvt.đttx.nhđt.ngôn ngữ lập trình c++[bookbooming.com]
Giáo trình nhập môn tin học đỗ thị mơ[bookbooming.com]

Viewers also liked (6)

PDF
đHtn.xây dựng website hỗ trợ học và thi toefl nguyễn thị diễm tiên[bookboom...
PDF
Can ban php[bookbooming.com]
PDF
Giao.trinh.solid work[bookbooming.com]
PDF
C++ dai hoc cong nghe[bookbooming.com]
DOC
Giao trinh oracle[bookbooming.com]
DOC
Giáo trình lập trình mạng đh đà lạt[bookbooming.com]
đHtn.xây dựng website hỗ trợ học và thi toefl nguyễn thị diễm tiên[bookboom...
Can ban php[bookbooming.com]
Giao.trinh.solid work[bookbooming.com]
C++ dai hoc cong nghe[bookbooming.com]
Giao trinh oracle[bookbooming.com]
Giáo trình lập trình mạng đh đà lạt[bookbooming.com]
Ad

Similar to Giao trinh visual studio[bookbooming.com] (20)

DOCX
Bai tap thuc hanh
PDF
Phat-trien-ung-dung-tren-Windows.pdf
DOC
Luận Văn Đề Cương Công Nghệ Thông Tin .Net Framework Và C#.doc
PDF
Giao trinh csharp tieng viet[bookbooming.com]
PDF
Giao trinh csharp tieng viet[bookbooming.com]
DOCX
5 lin q
PDF
Thuc HanhaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
PDF
Thuc Hanh_LT_Windows1(C#2010).aaaaaaaaaapdf
PDF
Thuc Hanh_LT_Windows1(C#2010)aaaaaaaaaaaaa (1).pdf
PDF
Thuc Hanh_LT_Windows1(C#2010) (AAAAAAAAAAAAAAAAAAAAA
PDF
Dotnet
DOC
Luận Văn Đề Cương Công Nghệ Thông Tin Lập Trình C For Windows.doc
PDF
Thêm sửa-xóa-combobox - c#
PDF
Tìm hiểu C# và Ứng dụng
PDF
Tim hieu c_sharp__va_ung_dung
PDF
Đề tài: Tìm hiểu ngôn ngữ C# và viết một ứng dụng minh họa, HAY
PDF
Tìm hiểu C# và Ứng dụng
PDF
Tim hieu c sharp va viet mot ung dung minh hoa
PDF
Pdf tim hieuc#vaungdung-mastercode.vn
Bai tap thuc hanh
Phat-trien-ung-dung-tren-Windows.pdf
Luận Văn Đề Cương Công Nghệ Thông Tin .Net Framework Và C#.doc
Giao trinh csharp tieng viet[bookbooming.com]
Giao trinh csharp tieng viet[bookbooming.com]
5 lin q
Thuc HanhaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
Thuc Hanh_LT_Windows1(C#2010).aaaaaaaaaapdf
Thuc Hanh_LT_Windows1(C#2010)aaaaaaaaaaaaa (1).pdf
Thuc Hanh_LT_Windows1(C#2010) (AAAAAAAAAAAAAAAAAAAAA
Dotnet
Luận Văn Đề Cương Công Nghệ Thông Tin Lập Trình C For Windows.doc
Thêm sửa-xóa-combobox - c#
Tìm hiểu C# và Ứng dụng
Tim hieu c_sharp__va_ung_dung
Đề tài: Tìm hiểu ngôn ngữ C# và viết một ứng dụng minh họa, HAY
Tìm hiểu C# và Ứng dụng
Tim hieu c sharp va viet mot ung dung minh hoa
Pdf tim hieuc#vaungdung-mastercode.vn
Ad

More from bookbooming1 (20)

PDF
Tập trung hay là chết
PDF
Edison mà tôi biết
PDF
Chinh phục các đợt sóng văn hóa
PDF
Chân dung mới của cfo cách nhà quản trị tài chính thay đổi vai trò của mình...
PDF
Bản lĩnh putin
PDF
Những công ty đột phá
PDF
Bí quyết của các ceo – 150 ceo toàn cầu tiết lộ bí mất về kinh doanh, cuộc số...
PDF
Tiểu sử steve jobs
PDF
Thuật đắc nhân tâm.
PDF
Con đường steve jobs
PDF
10 lời khuyên khởi nghiệp
PPT
Chuong3
PPT
Chuong mo dau
PPT
Chuong 5
PPT
Chuong 2
PPT
Chuong 1
PPT
Ch viii
PPT
Ch­ vii
PPT
Chuong 4
PPT
Tập trung hay là chết
Edison mà tôi biết
Chinh phục các đợt sóng văn hóa
Chân dung mới của cfo cách nhà quản trị tài chính thay đổi vai trò của mình...
Bản lĩnh putin
Những công ty đột phá
Bí quyết của các ceo – 150 ceo toàn cầu tiết lộ bí mất về kinh doanh, cuộc số...
Tiểu sử steve jobs
Thuật đắc nhân tâm.
Con đường steve jobs
10 lời khuyên khởi nghiệp
Chuong3
Chuong mo dau
Chuong 5
Chuong 2
Chuong 1
Ch viii
Ch­ vii
Chuong 4

Giao trinh visual studio[bookbooming.com]

  • 1. MỤC LỤC Phần 1 Cơ bản về ngôn ngữ lập trình C#...........................................................................................................................4 Bài thực hành 1.1. Chương trình đầu tiên........................................................................................................... 4 Tóm tắt.............................................................................................................................................................4 Kỹ thuật được trình bày...................................................................................................................................4 Trình tự thực hiện............................................................................................................................................4 Bài thực hành 1.2. Module hóa chương trình......................................................................................................7 Tóm tắt.............................................................................................................................................................7 Kỹ thuật được trình bày...................................................................................................................................7 Trình tự thực hiện............................................................................................................................................7 Bài thực hành 1.3. Tạo thư viện sử dụng chung.................................................................................................. 9 Tóm tắt.............................................................................................................................................................9 Kỹ thuật được trình bày...................................................................................................................................9 Trình tự thực hiện............................................................................................................................................9 Mở rộng.........................................................................................................................................................10 Bài thực hành 1.4. Tam giác Pascal ..................................................................................................................11 Tóm tắt...........................................................................................................................................................11 Kỹ thuật được trình bày.................................................................................................................................11 Trình tự thực hiện..........................................................................................................................................11 Mở rộng.........................................................................................................................................................12 Bài thực hành 1.5. Tam giác Pascal – array version.........................................................................................13 Tóm tắt...........................................................................................................................................................13 Kỹ thuật được trình bày.................................................................................................................................13 Trình tự thực hiện..........................................................................................................................................13 Mở rộng.........................................................................................................................................................13 Bài thực hành 1.6. MyTYPE...............................................................................................................................14 Tóm tắt...........................................................................................................................................................14 Kỹ thuật được trình bày.................................................................................................................................14 Trình tự thực hiện..........................................................................................................................................14 Mở rộng.........................................................................................................................................................14 Bài thực hành 1.7. Quản lý sinh viên.................................................................................................................15 Tóm tắt...........................................................................................................................................................15 Kỹ thuật được trình bày.................................................................................................................................15 Trình tự thực hiện..........................................................................................................................................15 Yêu cầu thêm.................................................................................................................................................20 Phần 2 Lập trình ứng dụng với winforms........................................................................................................................21 Bài thực hành 2.1 helloWinForms.....................................................................................................................21 Tóm tắt...........................................................................................................................................................21 Kỹ thuật được trình bày.................................................................................................................................21 Trình tự thực hiện..........................................................................................................................................21 Mở rộng.........................................................................................................................................................32 Bài thực hành 2.2 usingControls....................................................................................................................... 33 Tóm tắt...........................................................................................................................................................33
  • 2. Kỹ thuật được trình bày.................................................................................................................................33 Trình tự thực hiện..........................................................................................................................................33 Bài thực hành 2.3 textFormat............................................................................................................................ 37 Tóm tắt...........................................................................................................................................................37 Kỹ thuật được trình bày.................................................................................................................................37 Trình tự thực hiện..........................................................................................................................................37 Mở rộng.........................................................................................................................................................44 Bài thực hành 2.4 myCalculator........................................................................................................................45 Tóm tắt...........................................................................................................................................................45 Kỹ thuật được trình bày:...............................................................................................................................45 Trình tự thực hiện..........................................................................................................................................45 Mở rộng.........................................................................................................................................................49 Bài thực hành 2.5 myNotePAD...........................................................................................................................50 Tóm tắt...........................................................................................................................................................50 Kỹ thuật được trình bày.................................................................................................................................50 Trình tự thực hiện..........................................................................................................................................50 Mở rộng.........................................................................................................................................................54 Bài thực hành 2.6 Quản lý sinh viên - WinForms version.................................................................................55 Tóm tắt...........................................................................................................................................................55 Kỹ thuật được trình bày.................................................................................................................................56 Trình tự thực hiện..........................................................................................................................................56 Mở rộng.........................................................................................................................................................61 Bài thực hành 2.7 myFileViewer........................................................................................................................62 Tóm tắt...........................................................................................................................................................62 Kỹ thuật được trình bày.................................................................................................................................62 Trình tự thực hiện..........................................................................................................................................62 Mở rộng.........................................................................................................................................................72 Phần 3 Xử lý dữ liệu với ADO.NET.................................................................................................................................73 Kiến thức cơ bản về ADO.NET 2.0....................................................................................................................73 3.1 Kiến trúc tổng quan của ADO.NET........................................................................................................73 3.2 Tổng quan về các mô hình xử lý dữ liệu trong ADO.NET: Mô hình Kết nối (Connected Model) và Mô hình Ngắt Kết nối (Disconnected Model)......................................................................................................75 3.3 Làm việc với mô hình Kết nối trong ADO.NET.....................................................................................78 3.3.4 Ví dụ.....................................................................................................................................................89 3.5 Làm việc với mô hình Ngắt kết nối: DataSet và DataTable....................................................................96 3.5 Sử dụng Data Binding...........................................................................................................................113 3.6 Lựa chọn giữa mô hình Kết nối và mô hình Ngắt kết nối.....................................................................113 3.6 Tạo đối tượng DataSet...........................................................................................................................115 3.7 Kết hợp giữa nhiều bảng.......................................................................................................................118 3.8 Thay đổi các bản ghi của cơ sở dữ liệu.................................................................................................123 3.9 Truy cập và hiển thị dữ liệu...................................................................................................................124 3.10 Cập nhật một dòng dữ liệu...................................................................................................................124 3.11 Xóa một dòng dữ liệu..........................................................................................................................126 3.12 Tạo một dòng dữ liệu mới................................................................................................................... 127 Phần 4 Xây dựng ứng dụng Web với WebForms..........................................................................................................139 4.1 Tìm hiểu về Web Forms..............................................................................................................................139 Bài tập thực hành Chuyên đề Visual Studio .NET 2
  • 3. 4.2 Các sự kiện của Web Forms.......................................................................................................................139 4.2.1 Sự kiện PostBack và Non-PostBack...................................................................................................140 4.2.2 Trạng thái của ứng dụng Web (State).................................................................................................140 4.2.3 Chu trình sống của một Web-Form....................................................................................................140 4.3 Điều khiển xác nhận hợp............................................................................................................................143 4.4 Một số ví dụ mẫu minh họa........................................................................................................................143 4.5 Các dịch vụ Web.........................................................................................................................................158 Phần 5 Phụ lục..................................................................................................................................................................163 Phụ lục A Chuỗi kết nối cho các loại nguồn dữ liệu........................................................................................163 Phụ lục B Bảng tương quan/chuyển đổi kiểu dữ liệu ở .NET Framework với các Data Provider..................163 Phần Tài liệu tham khảo...............................................................................................................................................165 Bài tập thực hành Chuyên đề Visual Studio .NET 3
  • 4. PHẦN 1 CƠ BẢN VỀ NGÔN NGỮ LẬP TRÌNH C# NH Bài thực hành 1.1. Chương trình đầu tiên Tóm tắt Bài thực hành này giúp bạn làm quen với môi trường Visual Studio 2005 và các thao tác nhập xuất cơ bản thông qua giao diện bàn phím. Cụ thể, chương trình yêu cầu người sử dụng nhập hai số, sau đó in ra màn hình tổng, tích và thương của hai số này. Kỹ thuật được trình bày - Làm quen với môi trường Visual Studio 2005. Cấu trúc một solution, project và các tài nguyên có liên quan - Cách thức sử dụng thư viện MSDN để tra cứu, hướng dẫn - Sử dụng thao tác nhập xuất cơ bản Trình tự thực hiện 1. Khởi động Microsoft Visual Studio 2005. Nhấn Ctrl + Shift + N hoặc chọn menu tương ứng là File  New  Project để tạo mới một project 2. Chọn loại ứng dụng cần phát triển là Visual C#  Console Application. Chọn thư mục chứa project và đặt tên cho project. Về mặt thực chất, Visual Studio coi project thuộc về một solution nào đó, và một solution có thể chứa nhiều Bài tập thực hành Chuyên đề Visual Studio .NET 4
  • 5. project. Tuy nhiên, trong nhiều “bài toán” đơn giản (như ví dụ của chúng ta chẳng hạn), một solution chỉ có 1 project. 3. Đặt tên cho project của chúng ta thành firstApp. Sau khi nhấn nút OK, hãy khảo sát xem cấu trúc của thư mục chứa solution của chúng ta. Bạn phải luôn nắm chắc về ý nghĩa của các tập tin, thư mục được tạo ra trong quá trình làm việc. 4. Gõ mã lệnh như minh họa vào trong phần mã nguồn của tập tin Program.cs 5. Sử dụng MSDN để tra cứu các thông tin bạn chưa biết về: a. Console và các phương thức ReadLine(), WriteLine() của nó b. Cách chuyển đổi kiểu chuỗi thành số, ví dụ như int.Parse() 6. Nhấn Ctrl + F5 để thực hiện chạy chương trình. Sau đó quan sát cấu trúc thư mục của solution, cho biết sự thay đổi của nó so với khi mới được tạo ra ở bước 3. 7. Thử thay đổi kết câu lệnh float thuong = (float)x / y; thành float thuong = x / y; rồi chạy chương trình, quan sát kết quả và rút ra kết luận. Bài tập thực hành Chuyên đề Visual Studio .NET 5
  • 6. 8. Sử dụng thêm các cấu trúc lệnh khác để tinh chỉnh hoạt động của chương trình (xử lý phép chia cho 0, …) Bài tập thực hành Chuyên đề Visual Studio .NET 6
  • 7. Bài thực hành 1.2. Module hóa chương trình Tóm tắt Viết chương trình nhập vào một số nguyên N từ bàn phím. Sau đó a. In ra màn hình giá trị N!. b. Nhập thêm một số nguyên K từ bàn phím. Sau đó in ra CKN = N!/(K!*(N-K)!) Kỹ thuật được trình bày - Cấu trúc, cách quản lý logic và vật lý, cách làm việc của solution và project - Thực hiện chia nhỏ ứng dụng thành để chuyên môn hóa các phần - Cơ bản về các kiểu phương thức trong một lớp Trình tự thực hiện 1. Mở solution đã làm ở Bài thực hành 1.1. Chỉnh sửa tên của solution từ “firstApp” thành “day1” cho có ý nghĩa. Xem cấu trúc thư mục của solution sau khi thay đổi. 2. Thêm một project vào solution này bằng menu lệnh File  Add  New project… . Tương tự như cách tạo mới project ở bài thực hành trước, chọn thể loại project là Console Application. Đặt tên cho project mới là “modular”. 3. Quan sát cấu trúc cây thư mục của solution trong cửa sổ Solution Explorer và cả trong Windows Explorer. Để ý rằng, trong cửa sổ Solution Explorer, project firstApp được tô đậm. Điều này có nghĩa, firstApp đóng vai trò là “Startup Bài tập thực hành Chuyên đề Visual Studio .NET 7
  • 8. project”. Khi nhấn Ctrl + F5 thì project này sẽ được gọi thực thi chứ không phải là project modular mà ta mới tạo ra. Trong cửa sổ Solution Explorer, nhắp phải chuột lên “modular”. Trong menu hiện ra, chọn menu lệnh “Set as Startup project” để thiết lập lại startup project cho solution. 4. Việc nhập n, tính n! rồi in kết quả bạn hoàn toàn có thể thực hiện được bằng các câu lệnh đơn giản. Tuy nhiên, để tăng tính rõ ràng và tái sử dụng, bạn nên tạo ra một phương thức để hỗ trợ việc tính toán n!. Xem mã lệnh bên dưới 5. Chạy thử chương trình để xem kết quả. Hãy để ý rằng, khai báo phương thức giaiThua là static long giaiThua(int n). Thử xóa static trong khai báo này rồi chạy lại chương trình.  Lỗi nhận được cho biết chỉ các phương thức static mới được triệu gọi, sử dụng lẫn nhau 6. Bằng cách tạo ra phương thức long giaiThua() như trên, chúng ta có thể giải quyết được vấn đề tính Ckn một cách dễ dàng. Lời gọi để tính Ckn như sau: GiaiThua(n)/(GiaiThua(n-k)*GiaiThua(k)) 7. Hãy tạo ra một phương thức để tính tổ hợp chập k của n phần tử (bạn tự quyết định các tham số và kiểu dữ liệu trả về). Bài tập thực hành Chuyên đề Visual Studio .NET 8
  • 9. Bài thực hành 1.3. Tạo thư viện sử dụng chung Tóm tắt Trong thực tế, một ứng dụng có thể là có khả năng thực thi (executable) hoặc chỉ đơn thuần là thư viện để chứa các chức năng, lớp đối tượng. Bài thực hành này hướng dẫn bạn tạo thư viện chứa các phương thức thường dùng. Với mục đích minh họa, thư viện này chỉ chứa 2 hàm tiện ích giúp tính giai thừa và tổ hợp chập. Sau khi biên dịch, bạn sẽ có được một file nhị với phần mở rộng là DLL. Thư viện này, khi cần, sẽ được tham chiếu đến trong các ứng dụng khác. Kỹ thuật được trình bày - Tạo loại ứng dụng loại thư viện Trình tự thực hiện 1. Tạo mới một project, đặt tên là commonUtils (common utilities - các tiện ích dùng chung). Chú ý chọn loại ứng dụng cần tạo là Class Library 2. Mặc định Visual Studio 2005 sẽ tạo ra trong namespace CommonUtils một lớp tên là Class1. Đổi tên lớp này lại thành Math. Sau đó cài đặt các phương thức như sau: 3. Rõ ràng, đây không phải là một chương trình để chạy như các ứng dụng bạn đã viết trước đó - class Math không có phương thức static public Main() – tức là bạn không thể nhấn Ctrl + F5 để chạy chương trình. Biên dịch project này bằng menu lệnh Build  Build commonUtils. Kết quả, bạn sẽ có một thư viện commonUtils.dll trong thư mục binRelease hoặc binDebug của project tùy Bài tập thực hành Chuyên đề Visual Studio .NET 9
  • 10. theo cách chọn chế độ biên dịch. Thư viện này sẽ được dùng để tham chiếu đến trong các ứng dụng cần nó. Mở rộng Bổ sung các phương thức thường dùng khác vào thư viện, chẳng hạn như phương thức xác định xem một số có phải là nguyên tố hay không, phương thức hoán đổi giá trị của hai số cho trước, … Bài tập thực hành Chuyên đề Visual Studio .NET 10
  • 11. Bài thực hành 1.4. Tam giác Pascal Tóm tắt Viết chương trình nhập một số nguyên N từ bàn phím, sau đó in ra màn hình N dòng đầu tiên của tam giác Pascal. Kỹ thuật được trình bày - Sử dụng thư viện có sẵn Trình tự thực hiện 1. Tạo mới một ứng dụng kiểu Console Application. Đặt tên project là pascalTriangle1 2. Thực hiện bổ sung tham khảo đến thư viện commonUtils bằng cách: - Nhắp phải chuột vào project pascalTriangle1 trong cửa sổ Solution Explorer - Trong menu hiện ra, chọn Add Reference… Trong tab Browse của hộp thoại Add Reference, tìm đến thư viện commonUtils.dll đã tạo ra trước đó.  Dễ thấy rằng thư viện được tham khảo đến không chỉ có dạng DLL mà có thể có các dạng khác, bao gồm EXE, OCX, … Bài tập thực hành Chuyên đề Visual Studio .NET 11
  • 12. 3. Hoàn thiện phần mã nguồn có sử dụng tham chiếu đến thư viện vừa bổ sung như hình dưới: Mở rộng Hãy tự rút ra những ghi chú cần thiết về việc: - Khai báo phương thức C(int n, int k) trong commonUtils là public static long C(int n, int k) static, public ở đây có ý nghĩa gì, có thể thay thế hoặc bỏ đi? - Tương tự cho phương thức giaiThua(int n); - Tại sao trong quá trình sử dụng phương thức C() lại phải ghi đầy đủ là commonUtils.Math.C()? Chỉ cần ghi Math.C() có được không? Bài tập thực hành Chuyên đề Visual Studio .NET 12
  • 13. Bài thực hành 1.5. Tam giác Pascal – array version Tóm tắt Sử dụng array để xây dựng tam giác Pascal như Bài thực hành 1.4. Kỹ thuật được trình bày - Sử dụng array Trình tự thực hiện 1. Tạo mới một project kiểu Console Application với tên là pascalTriangle2 2. Sử dụng các tính chất C00 = Ckk = 1, Cnk = Cn-1k-1 + Cn-1k , ta sẽ xây dựng một jagged array từ nhỏ đến lớn. Chi tiết như phần mã nguồn phía dưới: Mở rộng Có thể dùng array nhiều chiều trong trường hợp này không? Nếu có thì có sự khác nhau nào so với dùng jagged array? Bài tập thực hành Chuyên đề Visual Studio .NET 13
  • 14. Bài thực hành 1.6. MyTYPE Tóm tắt Viết chương trình in nội dung văn bản ra màn hình (như lệnh TYPE ở Ms DOS). Tên file được truyền theo tham số dòng lệnh. Kỹ thuật được trình bày - Sử dụng tham số dòng lệnh được truyền vào - Sử dụng namespace System.IO để đọc nội dung tập tin Trình tự thực hiện 1. Tạo mới một projecet kiểu Console Application với tên myTYPE 2. Khai báo sử dụng thêm namespace System.IO rồi hoàn thiện phần mã nguồn như minh họa dưới Mở rộng - Cách kiểm tra sự tồn tại của tập tin trước khi đọc và hiển thị nó như trên đã an toàn chưa? Có trường hợp nào mà sự tồn tại của tập tin lại chưa đảm bảo cho việc đọc và hiển thị nó? Giải quyết bằng cách nào? - Thêm phần kiểm tra số lượng tham số truyền vào dòng lệnh để chương trình có thể hoạt động chính xác hơn (sử dụng args.Length) - Sử dụng MSDN để tìm hiểu thêm các lớp khác trong namespace System.IO Bài tập thực hành Chuyên đề Visual Studio .NET 14
  • 15. Bài thực hành 1.7. Quản lý sinh viên Tóm tắt Viết chương trình quản lý sinh viên của một trường. Sinh viên có thể học các chuyên ngành Công nghệ Thông tin, Vật lý, Ngữ văn. Mỗi chuyên ngành tương ứng có các môn học khác nhau. • Sinh viên khoa Công nghệ Thông tin phải học 3 môn Pascal, C# và SQL. • Sinh viên khoa Vật lý phải học 4 môn: Cơ học, Điện học, Quang học, Vật lý hạt nhân. • Sinh viên khoa Văn phải học 2 môn Văn học cổ điển và Văn học Hiện đại Chương trình cho phép nhập danh sách sinh viên, sau đó in danh sách sinh viên cùng với điểm trung bình của họ ra màn hình. In ra danh sách những sinh viên có điểm trung bình cao trên 5.0 ra màn hình. Thông tin hiển thị có dạng Họ tên, Chuyên ngành đào tạo, Điểm trung bình. Kỹ thuật được trình bày - Truy xuất tập tin có định dạng cho trước - Sử dụng một phương thức của lớp String - Các kỹ thuật hướng đối tượng được sử dụng trong bài toán thực tế Trình tự thực hiện 1. Trước khi tiến hành cài đặt, ta khảo sát qua sơ đồ lớp được sử dụng. Với những mô tả khá rõ ràng trong yêu cầu bài toán, ta có được cái nhìn tổng quan về các lớp như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 15
  • 16. Lưu ý rằng, phương thức dtb() được cài đặt là virtual để chúng ta có thể override một cách cụ thể, chi tiết hơn trong các lớp kế thừa từ class SinhVien. Phương thức ToString() được cài đặt override từ lớp object để sử dụng trong việc in “nội dung” của đối tượng. 2. Tạo mới một project kiểu Console Application với tên là studentManager 3. Tại cây phân cấp Solution Explorer nhắp phải chuột và chọn Add New Item… Trong hộp thoại hiện ra, chọn tạo mới class SinhVien.cs Bài tập thực hành Chuyên đề Visual Studio .NET 16
  • 17. 4. Cài đặt các thành phần cơ bản cho lớp SinhVien Bài tập thực hành Chuyên đề Visual Studio .NET 17
  • 18. 5. Bổ sung thêm các class SinhVienCNTT, SinhVienVan, SinhVienVL theo phân tích thiết kế lớp từ trước. Dưới đây là phần mô tả cài đặt cho lớp SinhVienVan. Hai lớp còn lại SinhVienCNTT, SinhVienVL được cài đặt một cách tương tự. Bài tập thực hành Chuyên đề Visual Studio .NET 18
  • 19. 6. Trong phần chương trình (tập tin Program.cs) chúng ta thực hiện yêu cầu bài toán như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 19
  • 20. Yêu cầu thêm - In ra 3 sinh viên có điểm trung bình cao nhất trường. - Chỉnh sửa để người sử dụng có thể nhập danh sách mà không biết trước số lượng sinh viên (sử dụng vòng lặp while, do, …) - Chỉnh sửa để có thể nhập dữ liệu các sinh viên từ file. Bài tập thực hành Chuyên đề Visual Studio .NET 20
  • 21. PHẦN 2 LẬP TRÌNH ỨNG DUNG VỚI WINFORMS NH NG ̣ NG Phần này kéo dài trong 2 buổi. Các bài thực hành này sẽ được giới thiệu như là nội dung cơ bản nhất mà sinh viên cần nắm. Các kỹ thuật khác sinh viên tự tham khảo và trao đổi với lớp. Bài thực hành 2.1 helloWinForms Tóm tắt Chương trình Kỹ thuật được trình bày - Cấu trúc của và cơ chế hoạt động của một project Windows Form Application. - Cơ chế xử lý sự kiện của các Control trong một Windows Form - Một số phương thức, thuộc tính, sự kiện quan trọng của các điều khiển trong một Windows Form. Trình tự thực hiện 1. Tạo mới một ứng dụng kiểu Windows Form Application với tên là 01-helloWindowsForm như hình vẽ Bài tập thực hành Chuyên đề Visual Studio .NET 21
  • 22. 2. Theo mặc định, một solution với một project được tạo ra. Project này có một lớp Form1. Khảo sát nội dung của project trong Windows Explorer, chúng ta sẽ thấy cấu trúc của thư mục và các tập tin tương tự như hình dưới: Có thể thấy, mỗi Form được tạo ra tương ứng với 3 tập tin có tiếp đàu ngữ là giống nhau, lấy ví dụ là Form1 • Form1.Designer.cs: chứa các mã lệnh do Form Designer tự sinh ra tương ứng với các thao tác do người sử dụng kéo thả các Control từ ToolBox vào bề mặt Form hay thực hiện các thiết lập đối với các Control. Bài tập thực hành Chuyên đề Visual Studio .NET 22
  • 23. Form1.cs: chứa phần mã lệnh và khai báo thêm do người sử dụng cài đặt. • Form1.resx: chứa các mô tả, khai báo về các tài nguyên được sử dụng trong Form. 3. Chúng ta cũng có thể quan sát cấu trúc của solution hay project bằng cách khảo sát cửa sổ Solution Explorer: 4. Từ cửa sổ Solution Explorer, đổi tên tập tin Form1.cs thành FormMain.cs. Để ý rằng, cả ba tập tin liên quan đến Form1 đều được thay đổi theo một cách đồng bộ. 5. Thiết kế giao diện cho FormMain như hình vẽ Bài tập thực hành Chuyên đề Visual Studio .NET 23
  • 24. 6. Bước tiếp theo, chúng ta sẽ thực hiện cài đặt phương thức xử lý sự kiện Click của nút bấm btnCurrentTime: a. Chọn điều khiển nút bấm btnCurrentTime trong cửa số thiết kế Form. b. Ở trang Event trong cửa sổ Properties Windows, nhắp đúp chuột vào sự kiện Click (xem hình vẽ dưới). Form Designer sẽ sinh ra phương thức xử lý sự kiện có tên mặc định là btnCurrentTime_Click(…). (Phương thức xử lý sự kiện được mặc định đặt tên là <tênĐiềuKhiển>_<TênSựKiện>) Soạn thảo phần mã lệnh cho phương thức này như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 24
  • 25. 7. Thực hiện chạy chương trình, khi nhấn vào nút bấm btnCurrentTime, một hộp thông báo được hiển thị ra như hình vẽ 8. Thực ra chúng ta có thể tự đặt tên cho phương thức xử lý sự kiện. Chẳng hạn, để cài đặt phương thức xử lý sự kiện MouseEnter cho nút bấm btnCurrentTime, trong cửa sổ Properties ở trang Events, tìm đến mục MouseEnter và: a. Nhập vào tên phương thức xử lý sự kiện: btn_MouseEnter b. Nhấn Enter c. FormDesigner sẽ tạo ra phương thức với tên tương ứng Bài tập thực hành Chuyên đề Visual Studio .NET 25
  • 26. d. Tiến hành cài đặt mã lệnh cho phương thức xử lý sự kiện trên như sau: private void btn_MouseEnter(object sender, EventArgs e) { btnCurrentTime.ForeColor = Color.Red; } 9. Tương tự, chúng ta cài đặt tiếp phương thức xử lý sự kiện MouseLeave cho nút bấm btnCurrentTime như sau private void btn_MouseLeave(object sender, EventArgs e) { btnCurrentTime.ForeColor = SystemColors.ControlText; } 10. Chạy chương trình và quan sát kết quả: Điều khiển nút bấm btnCurrentTime sẽ có hiệu ứng mouse hover khá ấn tượng: khi rê con trỏ chuột vào nút bấm btnCurrentTime, màu chữ của nó sẽ đổi sang màu đỏ; màu chữ của nút bấm trở thành bình thường (màu ControlText) khi con trỏ chuột rê ra khỏi nút bấm. 11. Để tìm hiểu kỹ hơn bản chất của việc gắn kết phương thức xử lý sự kiện, chúng ta nhắp đúp chuột vào FormMain.Designer.cs trong cửa sổ Solution Explorer để xem phần nội dung được sinh ra bởi Form Designer: Bài tập thực hành Chuyên đề Visual Studio .NET 26
  • 27. Chú ý những phần được tô sáng trong hình vẽ nói trên; từ đó suy ra được bản chất của việc gắn kết phương thức xử lý sự kiện trong khi thiết kế. Bài tập thực hành Chuyên đề Visual Studio .NET 27
  • 28. 12. Đóng file nội dung FormMain.Designer.cs lại. Các bước tiếp theo sẽ minh họa cách thức dùng chung một phương thức xử lý sự kiện cho nhiều đối tượng khác nhau. 13. Trong cửa sổ thiết kế của FormMain, thực hiện a. Chọn cả hai đối tượng btnClose và btnAbout b. Trong trang Events của cửa sổ Properties, gõ tên phương thức xử lý sự kiện Click cho cả hai điều khiển nút bấm này là btnTask_Click rồi nhấn Enter (xem hình vẽ) 14. Thực hiện cài đặt mã lệnh cho phương thức này như sau: private void btnTask_Click(object sender, EventArgs e) { if (sender == btnClose) this.Close(); else if (sender == btnAbout)1 MessageBox.Show("Day la chuong trinh minh hoa", "Thong bao"); } Trong phương thức trên, chúng ta sử dụng đối số sender để nhận biết điều khiển nào phát sinh sự kiện. Chúng ta cũng có thể thực hiện như thế này: 1 Thực ra không nhất thiết phải có nhánh else if, chỉ cần else là đủ, bởi vì ở đây chúng ta chỉ áp dụng phương thức này cho hai điều khiển btnClose và btnAbout!. Bài tập thực hành Chuyên đề Visual Studio .NET 28
  • 29. private void btnTask_Click(object sender, EventArgs e) { string stTask = (sender as Button).Text; 2 if (stTask == "Close") this.Close(); else if (stTask == "About") MessageBox.Show("Day la chuong trinh minh hoa", "Thong bao"); } 15. Bây giờ, chúng ta tinh chỉnh thêm để chương trình hỗ trợ hiệu ứng mouse hover cho tất cả các điều khiển trong form: a. Sửa lại phần mã nguồn cho 2 phương thức xử lý sự kiện btn_MouseEnter và btn_MouseLeave như sau: private void btn_MouseEnter(object sender, EventArgs e) { (sender as Control).ForeColor = Color.Red; } private void btn_MouseLeave(object sender, EventArgs e) { (sender as Control).ForeColor = SystemColors.ControlText; } b. Trong phần FormDesigner, chọn tất cả các đối tượng trên bề mặt Form. c. Trong cửa sổ Properties, chọn phương thức xử lý sự kiện MouseLeave cho tất cả các đối tượng đang chọn là btn_MouseLeave (xem hình vẽ) 2 Phép chuyển kiểu (sender as Button) trong câu lệnh này là thành công vì cả btnClose và btnAbout đều là các điều khiển kiểu Button Bài tập thực hành Chuyên đề Visual Studio .NET 29
  • 30. d. Làm tương tự để gán phương thức xử lý sự kiện MouseEnter cho tất cả các điều khiển nói trên là btn_Enter. e. Chạy chương trình để xem hiệu ứng: khi rê con trỏ chuột qua các điều khiển, font chữ của chúng sẽ được đổi thành màu đỏ. 16. Trong bước 11, chúng ta đã biết được cách thức đưa một thành phần điều khiển vào giao diện của một Windows Form thông qua mã lệnh (bằng cách tìm hiểu phần mã sinh ra bởi Form Designer). Bây giờ, chúng ta sẽ áp dụng để thực hiện thêm các điều khiển vào Form và gán phương thức xử lý sự kiện cho chúng trong thời gian thực thi chương trình a. Bổ sung vào Form một nút bấm btnCreateButton b. Cài đặt phương thức xử lý sự kiện Click cho nút bấm này như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 30
  • 31. c. Chạy chương trình và quan sát kết quả. Bài tập thực hành Chuyên đề Visual Studio .NET 31
  • 32. Mở rộng - Hãy tìm hiểu ý nghĩa của việc cài đặt mã lệnh ở bước 15.a: (sender as Control). Có thể sử dụng phép ép kiểu nào khác không? Tại sao? - Điều chỉnh trong giao diện chương trình, trong đó có một số điều khiển (Label, TextBox, RadioButton, CheckBox hoặc Button) sử dụng màu khác với màu mặc định (là SystemColors.ControlText). Khi đó, hiệu ứng mouse hover hoạt động không đúng nữa. Hãy chỉnh sửa chương trình để khắc phục phát sinh này. Bài tập thực hành Chuyên đề Visual Studio .NET 32
  • 33. Bài thực hành 2.2 usingControls Tóm tắt Xây dựng chương trình điền thông tin cá nhân như minh họa Kỹ thuật được trình bày - Giới thiệu một ứng dụng WinForms cơ bản - Cách thức lưu file với nội dung tiếng Việt - Các thành phần điều khiển cơ bản: Button, Label, TextBox, PictureBox, Timer, … - Nạp một ảnh từ file Trình tự thực hiện 1. Tạo mới một project loại Windows Application, đặt tên là usingControls 2. Theo mặc định, một lớp Form1 được sinh ra. Chỉnh sửa các thuộc tính của Form1 với các giá trị như bảng dưới: Thuộc tính Giá trị Ghi chú Name FormMain Text Hello WinForms Tiêu để của cửa sổ FormBorderStyl FixedSingle Kích thước của cửa sỗ sẽ không được e thay đổi khi chạy chương trình MaximizeBox False Vô hiệu hóa nút Maximize của cửa sổ Bài tập thực hành Chuyên đề Visual Studio .NET 33
  • 34. Chú ý rằng, những thuộc tính có thay đổi giá trị so với mặc định sẽ được hiển thị trong cửa sổ Properties dưới dạng chữ in đậm 3. Thiết kế giao diện của form như minh họa. Lưu ý, với mỗi điều khiển bạn đưa vào form, nếu dự định truy xuất nó trong phần mã nguồn khi lập trình thì hãy đặt tên nó thay vì để như tên mặc định. Chỉnh sửa thuộc tính của một số đối tượng như sau: Điều khiển Thuộc tính Giá trị dtpDOB Format Custom CustomFormat dd/MM/yyyy txtOther Enable False lblInfo Font Chọn font thích hợp, in đậm picImage SizeMode StretchImage lblName BackColor Transparent (Web) tmrScroll Interval 120 4. Nhấn Ctrl + S để lưu nội dung project. Do chúng ta có sử dụng ký tự tiếng Việt trong Form nên Visual Studio có hiển thị hộp thoại để yêu cầu chỉ định bảng mã lưu ký tự: Bài tập thực hành Chuyên đề Visual Studio .NET 34
  • 35. Nhấn nút “Save With Other Encoding” để chọn bảng mã thích hợp – sau đó bạn có thể chọn cách lưu theo UTF8 như hình dưới (cũng có thể chọn tùy chọn Unicode – Codepage 1200): 5. Cài đặt phần mã lệnh cho sự kiện Click của nút bấm btnSelectImage như sau: Khi người sử dụng nhấn vào nút này, một hộp thoại sẽ hiện ra cho phép chọn ảnh. Chỉ các tập tin có phần mở rộng là BMP, JPG, GIF mới được hiển thị để lựa chọn. Điều này được thiết lập thông qua thuộc tính Filter của đối tượng dlgOpen (thuộc lớp OpenFileDialog). Bài tập thực hành Chuyên đề Visual Studio .NET 35
  • 36. 6. Khi người sử dụng gõ tên của họ vào txtName thì nội dung của lblName cũng thay đổi theo. Muốn vậy, ta cài đặt mã lệnh cho sự kiện TextChanged của txtName như (1) – xem minh họa code ở dưới 7. Đối tượng txtOther chỉ được sử dụng (Enabled) khi mà chkOther được check vào, do đó ta cũng cài đặt mã lệnh cho sự kiện CheckChanged của chkOther như (2) 8. Khi nhấn nút “Cập nhật” thì nội dung của lblInfo được cập nhật theo như phần mã lệnh cài đặt cho sự kiện Click của btnUpdate (3) 9. Người sử dụng có thể bật tắt chế độ cuộn nội dung dòng chữ lblInfo bằng cách nhấn chuột vào nó. Cài đặt mã lệnh cho sự kiện Click của lblInfo như (5) 10. Để cuộn nội dung dòng chữ, cài đặt mã lệnh cho sự kiện Tick của tmrScroll như (4) Bài tập thực hành Chuyên đề Visual Studio .NET 36
  • 37. Bài thực hành 2.3 textFormat Tóm tắt Xây dựng chương trình thể hiện định dạng cho đoạn văn bản tĩnh (Label) Kỹ thuật được trình bày - Cách sử dụng Font, FontStyle trong ứng dụng Windows Form - Truy xuất các thành phần dữ liệu giữa các Form - Sử dụng cửa sổ dạng Dialog trong chương trình Trình tự thực hiện 1. Tạo mới một ứng dụng loại Windows Applications, đặt tên là textFormat 2. Theo mặc định, một lớp Form1 được sinh ra. Chỉnh sửa các thuộc tính của Form1 với các thuộc tính giá trị như hình dưới: Thuộc tính Giá trị Ghi chú Name FormMain Text Text formartting Tiêu để của cửa sổ FormBorderStyl FixedSingle Kích thước của cửa sỗ sẽ không được e thay đổi khi chạy chương trình MaximizeBox False Vô hiệu hóa nút Maximize của cửa sổ Chú ý rằng, những thuộc tính có thay đổi giá trị so với mặc định sẽ được hiển thị trong cửa sổ Properties dưới dạng chữ in đậm 3. Thiết kế giao diện cho FormMain như hình dưới đây STT Thuộc tính Giá trị Ghi chú 1 Name lblAdvert Label 2 Name pnlAdvert Panel 3 Name btnChangeText Button Text ChangeText… Bài tập thực hành Chuyên đề Visual Studio .NET 37
  • 38. 4 Name btnClose Button Text Close 4. Bổ sung vào Project thêm một WindowForm bằng cách chọn menu lệnh Project  Add Windows Forms… Chọn tên file của Windows Form cần tạo là FormSettings.cs như hình rên. 5. Thiết kế giao diện cho FormSettings vừa được tạo ra như hình dưới. Trình tự đặt các điều khiển vào form có thể thực hiện như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 38
  • 39. - Điều khiển TextBox đặt tên là txtAdvert. - Điều khiển Frame với thuộc tính Text là “Format” - 4 CheckBox có tên là chkB, chkI, chkU, chkS với thuộc tính Text được thiết lập tương ứng là “Bold”, “Italic”, “Underline” và “Strike out” - 4 RadioButton có tên là rbRed, rbGreen, rbBlue, rbYellow với thuộc tính Text được thiết lập tương ứng với 4 tên màu “Red”, “Green”, “Blue” và “Yellow” - 3 nút bấm btnOK, btnApply, btnCancel. Sau đó thiết lập thuộc tính DialogResult của các nút bấm btnOK, btnCancel lần lượt là OK và Cancel 6. Tiến hành cài đặt mã lệnh cho nút bấm btnChangeText ở FormMain như sau: private void btnChangeText_Click(object sender, EventArgs e) { FormSettings frm = new FormSettings(); if (frm.ShowDialog() == DialogResult.OK) { lblAdvert.Text = “You’ve clicked at OK button!”; } } 7. Thực hiện chạy chương trình và quan sát kết quả. Để ý rằng, với việc thiết lập thuộc tính DialogResult cho 2 nút bấm btnOK và btnCancel như bước 5 ở trên, chúng ta không cần cài đặt mã lệnh cho 2 nút này mà vẫn có hiệu ứng frm (một thể hiện của FormSettings) bị đóng khi một trong 2 nút này được nhấn. Hơn nữa, chúng ta sẽ biết được người sử dụng đã nhấn vào nút nào bằng cách kiểm tra kết quả của hàm frm.ShowDialog() như trên. Bài tập thực hành Chuyên đề Visual Studio .NET 39
  • 40. Trong Bước tiếp theo, chúng ta sẽ làm cho hai form sẽ tương tác dữ liệu được với nhau trong thời gian thực thi. 8. Trong FormMain, tạo ra một thuộc tính AdvertText kiểu string như sau: public string AdvertText { get { return lblAdvert.Text; } set { lblAdvert.Text = value; } } 9. Trong FormSettings, chúng ta thêm khai báo biến thành phần và sửa lại phương thức khởi dựng của lớp: public partial class FormSettings: Form { FormMain formMain; public FormSettings(FormMain frmMain) { InitializeComponent(); this.formMain = frmMain; } private void FormSettings_Load(object sender, EventArgs e) { this.txtAdvert.Text = formMain.AdvertText; } ..... ..... } 10. Cài đặt phương thức xử lý sự kiện Click của nút bấm btnApply như sau public void btnApply_Click(object sender, EventArgs e) { formMain.AdvertText = this.txtAdvert.Text; } Để ý rằng, bạn phải chỉnh sửa modifier của phương thức bntApply_Click thành public thay vì private như FormDesigner sinh ra theo mặc định. Điều này cho Bài tập thực hành Chuyên đề Visual Studio .NET 40
  • 41. phép phương thức này có thể được triệu gọi từ ngoài lớp FormSettings như ở bước 11 dưới đây. 11. Chỉnh sửa lại phương thức xử lý sự kiện Click của nút bấm btnChangeText ở FormMain như sau: private void btnChangeText_Click(object sender, EventArgs e) { FormSettings frm = new FormSettings(this); if (frm.ShowDialog() == DialogResult.OK) { frm.btnApply_Click(null, null); } } 12. Chạy thử chương trình và quan sát kết quả. Tóm lại, chúng ta đã thực hiện những bước sau đây để có thể thay đổi dữ liệu từ 2 form: a. Tạo property (AdvertText) có modifier kiểu public cho phần dữ liệu muốn truy xuất (lblAdvert.Text) b. Tìm cách truyền tham chiếu của form chứa property nói trên (FormMain) đến form muốn truy xuất (FormSettings) 13. Tiếp theo, chúng ta sẽ tạo ra property tên AdvertForeColor có kiểu Color trong lớp FormMain để thực hiện thay đổi màu sắc của lblAdvert: public Color AdvertForeColor { get { return lblAdvert.ForeColor; } set { lblAdvert.ForeColor = value; } } 14. Cập nhật lại phương thức xử lý sự kiện Click của nút bấm btnApply và sự kiện Load trong lớp FormSettings: private void FormSettings_Load(object sender, EventArgs e) { this.txtAdvert.Text = formMain.AdvertText; rbRed.Checked = formMain.AdvertForeColor == Color.Red; rbGreen.Checked = formMain.AdvertForeColor == Color.Green; rbBlue.Checked = formMain.AdvertForeColor == Color.Blue; rbYellow.Checked = formMain.AdvertForeColor == Color.Yellow; } Bài tập thực hành Chuyên đề Visual Studio .NET 41
  • 42. public void btnApply_Click(object sender, EventArgs e) { formMain.AdvertText = this.txtAdvert.Text; if (rbRed.Checked) formMain.AdvertForeColor = Color.Red; else if (rbGreen.Checked) formMain.AdvertForeColor = Color.Green; else if (rbBlue.Checked) formMain.AdvertForeColor = Color.Blue; else formMain.AdvertForeColor = Color.Yellow; } 15. Như vậy, trong các bước trên, chúng ta đã tạo ra một property kiểu Color và đồng bộ hóa nó với 4 điều khiển RadioButton. Những bước tiếp theo chúng ta tạo ra thêm một property khác để thay đổi định dạng font chữ cho lblAdvert. Trước hết, chúng ta cần biết a. Các thuộc tính như Bold, Italic, .. của lblAdvert.Font là chỉ đọc. Thế nên không thể thực hiện phép gán để thay đổi lblAdvert.Font.Bold được b. Việc thay đổi tính chất Bold, Italic, … của một đối tượng Font được thực hiện bằng cách tạo mới đối tượng Font (tham khảo thêm MSDN để biết 13 hàm nạp chồng để khởi tạo một đối tượng Font) c. Một trong các hàm nạp chồng khá đơn giản mà chúng ta có thể sử dụng để tạo một đối tượng Font có cú pháp là: Trong đó, family sẽ là tên font, emSize là kích cỡ font, style là kiểu font. Giá trị của style sẽ là sự tổng hợp theo phép toán or của các giá trị FontStyle.Bold, FontStyle.Italic, FontStyle.Underline và FontStyle.Strikeout. 16. Tạo property AdvertFontFormat trong lớp FormMain như sau: public bool[] AdvertFontFormat { get { Font f = lblAdvert.Font; return (new bool[] {f.Bold, f.Italic, f.Underline, f.Strikeout}); } set { if (value.Length == 4) { FontStyle fs = FontStyle.Regular; Bài tập thực hành Chuyên đề Visual Studio .NET 42
  • 43. if (value[0]) fs = fs | FontStyle.Bold; if (value[1]) fs = fs | FontStyle.Italic; if (value[2]) fs = fs | FontStyle.Underline; if (value[3]) fs = fs | FontStyle.Strikeout; lblAdvert.Font = new Font(lblAdvert.Font.Name, lblAdvert.Font.Size, fs); } } } Property AdvertFontFormat có kiểu dữ liệu là một mảng bool gồm 4 phần tử mà thứ tự của chúng sẽ tương ứng biểu diễn tính chất Bold, Italic, Underline và Strikeout của một FontStyle. 17. Tiếp đến, chúng ta sẽ cập nhật các phương thức cần thiết trong lớp FormSettings để sử dụng thuộc tính AdvertFontFormat vừa tạo như sau: private void FormSettings_Load(object sender, EventArgs e) { this.txtAdvert.Text = formMain.AdvertText; rbRed.Checked = formMain.AdvertForeColor == Color.Red; rbGreen.Checked = formMain.AdvertForeColor == Color.Green; rbBlue.Checked = formMain.AdvertForeColor == Color.Blue; rbYellow.Checked = formMain.AdvertForeColor == Color.Yellow; this.chkB.Checked = formMain.AdvertFontFormat[0]; this.chkI.Checked = formMain.AdvertFontFormat[1]; this.chkU.Checked = formMain.AdvertFontFormat[2]; this.chkS.Checked = formMain.AdvertFontFormat[3]; } public void btnApply_Click(object sender, EventArgs e) { formMain.AdvertText = this.txtAdvert.Text; if (rbRed.Checked) formMain.AdvertForeColor = Color.Red; else if (rbGreen.Checked) formMain.AdvertForeColor = Color.Green; else if (rbBlue.Checked) formMain.AdvertForeColor = Color.Blue; else formMain.AdvertForeColor = Color.Yellow; Bài tập thực hành Chuyên đề Visual Studio .NET 43
  • 44. formMain.AdvertFontFormat = new bool[] { chkB.Checked, chkI.Checked, chkU.Checked, chkS.Checked }; } 18. Chạy chương trình để xem kết quả. Mở rộng - Qua quá trình cài đặt, có thể thấy, Form Designer của Visual Studio .NET không hỗ trợ việc tạo mảng điều khiển giống như ở Visual Basic. Tuy nhiên, chúng ta có thể mô phỏng mảng điều khiển bằng cách tạo ra các Property hoặc là Indexer để ánh xạ đến các điều khiển. Bài tập thực hành Chuyên đề Visual Studio .NET 44
  • 45. - Bài thực hành 2.4 myCalculator Tóm tắt Xây dựng chương trình mô phỏng một máy tính điện tử đơn giản. Kỹ thuật được trình bày: - Xây dựng ứng dụng GUI với WinForms - Cách thức cài đặt, gắn kết, xử lý events với một thành phần giao diện - Tạo hiệu ứng transparent với Form Trình tự thực hiện 1. Tạo mới một project loại Windows Application, đặt tên là myCalculator 2. Theo mặc định, một lớp Form1 được sinh ra. Chỉnh sửa các thuộc tính của Form1 với các giá trị như bên dưới: Thuộc tính Giá trị Ghi chú Name FormMain Text my Calculator Tiêu để của cửa sổ FormBorderStyl FixedSingle Kích thước của cửa sỗ sẽ không được e thay đổi khi chạy chương trình MaximizeBox False Vô hiệu hóa nút Maximize của cửa sổ 3. Tạo giao diện cho chương trình như hình minh họa Sau khi đã tạo ra được giao diện với các điều khiển có vị trí, kích thước hợp lý, bạn nên cố định chúng. Thực hiện điều này bằng cách nhắp phải chuột lên bề mặt của FormMain, trong menu hiện ra, chọn Lock Controls. Điều chỉnh các thuộc tính của đối tượng “màn hình hiển thị” như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 45
  • 46. Đối tượng Thuộc tính Giá trị “Màn hình hiển thị” Name txtScreen Text 0 Font Chọn font thích hợp ReadOnly True TextAlign Right 4. Trước khi bắt tay vào viết mã lệnh, bạn phải hình dung cách thức làm việc của chương trình cũng như việc chuẩn bị các kiểu đối tượng, dữ liệu. Giao diện của chương trình chỉ là các nút bấm và “màn hình” hiển thị kết quả. Do chỉ là máy tính đơn giản nên chương trình chỉ hỗ trợ các phép tính 2 ngôi cơ bản. Có thể phân biệt nút bấm thuộc các nhóm: (1) Nút bấm chữ số 0, 1, …, 9, ký hiệu dấu chấm thập phân, nút đảo dấu, nút xóa trái  xác định hai toán hạng tham gia vào phép tính. (2) Nút bấm phép tính +, -, x, /  xác định phép tính (3) Nút Enter  để kích hoạt việc thực hiện phép tính và hiển thị kết quả (4) Nút C  khởi tạo một phép toán mới Trình tự các bước sử dụng của máy tính thông thường như sau: Người sử dụng “nhập vào” số thứ nhất, xác định phép toán, sau đó “nhập vào” số thứ hai, cuối cùng nhấn dấu = Khái niệm “nhập vào” ở đây có nghĩa là người sử dụng dùng liên tiếp các nút bấm thuộc nhóm (1). Chú ý rằng dấu chấm thập phân chỉ xuất hiện 1 lần trong 1 số hạng. Việc xác định phép toán có thể xác định nhiều lần giữa hai lần nhập hai số hạng. Tóm lại, khi người sử dụng nhấn một nút bấm trong nhóm (1), ta luôn biết được anh ta đang “nhập số liệu” cho số hạng nào: nếu chưa xác định phép toán thì có nghĩa anh ta đang nhập cho số thứ nhất, ngược lại thì đang nhập cho số thứ hai. Điều khác nữa cần phải tính đến, đó là lưu trữ dữ liệu như thế nào? Trong bài toán này, việc sử dụng string để lưu trữ các số hạng là hợp lý cho việc nhập số. Phép tính sẽ được lưu trữ bằng một ký tự. 5. Ngay trong đầu phần mã nguồn partial class FormMain: Form, bổ sung khai báo các biến a. stNum1, stNum2 kiểu string để đại diện cho hai chuỗi số hạng. b. op kiểu char để đại diện cho phép toán (op là viết tắt của operator) 6. Cài đặt phần mã lệnh cho sự kiện FormLoad và sự kiện click của nút C như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 46
  • 47. 7. Các nút bấm trong nhóm (1) được cài đặt phần mã lệnh tương ứng với sự kiện click như sau: 8. Phương thức xử lý sự kiện click của nút bấm thuộc nhóm (2) – xác định phép tính 9. Cuối cùng, phương thức xử lý sự kiện click của nút bấm ‘=’ được cài đặt như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 47
  • 48. 10. Đến đây, các tính năng cơ bản của máy tính đã được cài đặt. Chúng ta khảo sát thêm một số hiệu ứng đặc biệt của lớp đối tượng Form: a. Thiết lập thuộc tính TopMost bằng True để trong thời gian thực thi, cửa sổ chương trình luôn luôn nổi. b. Bổ sung phương thức xử lý sự kiện Activated và Deactive cho FormMain. Hai sự kiện này được kích hoạt tương ứng khi người dùng chuyển focus từ vào ứng dụng (Activated) hay ra khỏi ứng dụng (Deactive) c. Minh họa khi chạy chương trình: khi người sử dụng chuyển focus ra khỏi cửa sổ ứng dụng, cửa sổ này sẽ bị mờ đi 50%. Bài tập thực hành Chuyên đề Visual Studio .NET 48
  • 49. Mở rộng - Cài đặt thêm các tính năng khác cho máy tính, chẳng hạn hỗ trợ phép tính 1 ngôi như căn bậc 2, nghịch đảo, phần trăm,… Chú ý phần xử lý lỗi (ví dụ như chia cho 0, căn bậc hai với số âm…) - Hỗ trợ để người sử dụng có thể nhập dữ liệu bằng bàn phím. Bài tập thực hành Chuyên đề Visual Studio .NET 49
  • 50. Bài thực hành 2.5 myNotePAD Tóm tắt Chương trình có các chức năng cơ bản của một chương trình soạn thảo văn bản đơn giản (như Ms Windows NotePAD): Tạo mới, Soạn thảo, Lưu lên đĩa, Mở từ đĩa. Chương trình cũng cho phép người sử dụng thiết lập các cấu hình phụ như: font chữ thể hiện (loại font, kích thước, màu sắc, ...). Kỹ thuật được trình bày - Sử dụng menu - Xây dựng ứng dụng với nhiều form - Lấy danh sách font hệ thống - Cách liên hệ giữa các đối tượng thành phần thuộc các form, module khác nhau Trình tự thực hiện 1. Tạo mới một project kiểu Windows Application, đặt tên là myNotePAD 2. Đổi thuộc tính Name của form thành FormMain 3. Bổ sung các thành phần StatusBarStrip, MenuStrip với các menu lệnh như minh họa. 4. Bổ sung thêm thành phần TextBox với tên gọi là txtDoc. Thiết lập các thuộc tính của txtDoc như sau: Thuộc tính Giá trị Multiline True Dock Fill Bài tập thực hành Chuyên đề Visual Studio .NET 50
  • 51. 5. Khai báo một biến fileName kiểu string ở đầu phần mô tả lớp. Biến này sẽ lưu tên file đang được soạn thảo. Sau đó cài đặt phương thức xử lý sự kiện cho tất cả các ToolStripMenuItem như sau:  Phần xử lý cho “else if (stTask == “Configuration…”)…” sẽ được cài đặt sau. Bài tập thực hành Chuyên đề Visual Studio .NET 51
  • 52. Chú ý rằng thuộc tính Text của các ToolStripMenuItem được sử dụng để viết các câu lệnh if ở trên. Vậy nên, bạn phải nhớ chính xác các thuộc tính Text của chúng để viết mã lệnh đúng. Thực ra, bạn có thể viết riêng rẽ cho từng phương thức xử lý sự kiện Click của từng ToolStripMenuItem, tuy nhiên theo ý kiến của riêng người viết giáo trình này, cách gộp chung như ở trên giúp bạn tập trung mã lệnh hơn. 6. Bước tiếp theo chúng ta cài đặt phần mã lệnh cho phép người sử dụng thay đổi font hiển thị tài liệu: Bổ sung một Form vào project bằng cách nhấn tổ hợp phím Ctrl + Shift + A. Một hộp thoại như sau hiện ra: Chọn loại đối tượng cần thêm là Windows Form, gõ tên file là “FormConfig.cs” rồi nhấn Add. Sau thao tác này, Visual Studio sẽ tạo ra một file trong đó có khai báo lớp FormConfig (trùng tên với tên file mà bạn cung cấp). Nếu muốn, chúng ta có thể đổi tên lớp của form này. 7. FormConfig sẽ làm nhiệm vụ hiển thị danh sách các fonts hiện có trong hệ thống để người sử dụng chọn. Thiết kế giao diện của FormConfig như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 52
  • 53. Dự kiến, khi form này bắt đầu nạp nó sẽ hiển thị danh sách tất cả các fonts đang được cài đặt trong hệ thống vào lstFonts. Font đang được sử dụng ở txtDoc (ở FormMain) sẽ được tô sáng trong lstFonts. Cuối cùng, khi người sử dụng nhấn nút OK, font được chọn trong lstFonts sẽ được sử dụng cho txtDoc. Như vậy, đã có sự sử dụng biến thành phần giữa hai forms: FormMain và FormConfig. 8. Trước hết, thiết lập thuộc tính DialogResult của hai nút OK và Cancel là OK và Cancel. Có thể thiết lập thêm các thuộc tính Anchor của các đối tượng để có giao diện hợp lý. 9. Thiết lập thuộc tính Modifiers của lstFonts là Public. Bằng cách này, chúng ta có thể truy xuất đến thành phần lstFonts từ bên ngoài. 10. Bổ sung biến thành phần stFontName kiểu string vào khai báo FormConfig, xem (1). 11. Sửa phương thức khởi dựng của lớp FormConfig như (2). 12. Viết mã cho phương thức xử lý sự kiện Load của form như (3). 13. Hoàn thiện phần gọi FormConfig ở FormMain như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 53
  • 54. Mở rộng - Cài đặt thêm các chức năng thường dùng khác đối với ứng dụng soạn thảo văn bản - Sửa lại ứng dụng theo giao diện MDI Bài tập thực hành Chuyên đề Visual Studio .NET 54
  • 55. Bài thực hành 2.6 Quản lý sinh viên - WinForms version Tóm tắt Thực hiện lại chương trình quản lý sinh viên như bài thực hành 1.7 nhưng giao tiếp với người sử dụng thông qua giao diện Windows. Chương trình có thể tạo mới, mở và lưu tập tin chứa dữ liệu về các sinh viên có định dạng như ví dụ sau: Nguyễn Văn Trung|True|1981/10/25 00:00:00|9|10|10 Trịnh Phương|False|1984/11/20 00:00:00|4|9 Trần Văn Phúc|True|1999/10/01 00:00:00|6|6.5|6.5|8 Đặng Phương Nam|True|1991/10/25 00:00:00|2|10 Trịnh Phương Lan|False|1994/11/20 00:00:00|2|9 Theo định dạng lưu này, mỗi dòng thông tin sẽ chứa các thông tin Họ tên, Giới tính, Ngày sinh, sau đó là các cột điểm. Nếu có 3 (tương ứng 2, 4) cột điểm thì sinh viên đó thuộc chuyên ngành CNTT (tương ứng Văn, Vật lý). (Xem lại Bài thực hành 1.7 để biết thêm về mô tả thông tin sinh viên). Khi người sử dụng chọn một sinh viên trong danh sách (danh sách này chỉ hiển thị tên sinh viên) thì các thông tin chi tiết về sinh viên này sẽ được hiển thị và cho phép chỉnh sửa tương ứng (xem hình vẽ). Chú ý rằng, tùy theo chuyên ngành của sinh viên, tên các môn học sẽ được hiển thị một cách hợp lý. Bài tập thực hành Chuyên đề Visual Studio .NET 55
  • 56. Kỹ thuật được trình bày - Sử dụng kỹ thuật hướng đối tượng trên ứng dụng GUI - Truy xuất file có định dạng quy ước sẵn Trình tự thực hiện 1. Tạo form có giao diện như minh họa. Đặt tên các thành phần cho hợp lý 2. Thiết kế các lớp đối tượng sinh viên như sơ đồ lớp được mô tả ở dưới. Các phương thức, thuộc tính của các lớp này đã được xây dựng trong các bài thực hành trước, ở đây ta chỉ thảo luận thêm về hàm ToFormatText() ở các lớp: Bài tập thực hành Chuyên đề Visual Studio .NET 56
  • 57. Trong lớp SinhVien, đặc tả hàm này như là một hàm ảo và không cho sử dụng trực tiếp từ thể hiện thuộc lớp SinhVien như sau: Đối với lớp SinhVienCNTT, hàm này được override lại thành: Tương tự, đối với lớp SinhVienVan và SinhVienVL, hàm này lần lượt được cài đặt như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 57
  • 58. 3. Xử lý sự kiện mở file dữ liệu, nạp vào listBox: o Bổ sung biến thành phần fileName để chứa tên file dữ liệu o Bổ sung thêm phương thức void updateFormTitle() o Thêm sự kiện Click cho nút Open như sau: Chú ý rằng, các lớp SinhVienVan, SinhVienCNTT, SinhVienVL đều phải override hàm ToString() để được hiển thị ngắn gọn trong hộp danh sách. 4. Xử lý sự kiện tạo mới file dữ liệu Bài tập thực hành Chuyên đề Visual Studio .NET 58
  • 59. a. Thêm phương thức xử lý sự kiện Click của nút New b. Gán phương thức xử lý sự kiện này cho sự kiện Load của Form 5. Khi người sử dụng chọn 1 sinh viên trong lstSinhVien, thông tin chi tiết về sinh viên này sẽ được hiển thị. Viết phương thức xử lý sự kiện SelectedIndexChanged của lstSinhVien như sau: 6. Viết phương thức xử lý sự kiện Click của nút “Cập nhật”, “Xóa” và “Bỏ cập nhật” như (1), (2), (3): Bài tập thực hành Chuyên đề Visual Studio .NET 59
  • 60. 7. Cài đặt phương thức xử lý sự kiện Click cho 4 nút “|<”, “<”, “>”, “>|” như sau: 8. Tiếp đến, cài đặt phương thức xử lý sự kiện Click của nút bấm Save và Save as… Bài tập thực hành Chuyên đề Visual Studio .NET 60
  • 61. Mở rộng - Thêm phần xử lý lỗi cho chương trình - Bổ sung chức năng Tạo mới Sinh viên Bài tập thực hành Chuyên đề Visual Studio .NET 61
  • 62. Bài thực hành 2.7 myFileViewer Tóm tắt Trong bài thực hành này, bạn sẽ được tiếp cận một số kỹ thuật nâng cao như: kế thừa form, thành phần điều khiển TreeView, ListView, ... Ứng dụng cuối cùng được tạo ra sẽ là ứng dụng quản lý tập tin kiểu ACDSee. Kỹ thuật được trình bày - Thừa kế form - Làm quen với các component, các thư viện nâng cao Trình tự thực hiện 1. Tạo mới một ứng dụng kiểu Windows Application với tên là fileViewer. Đặt tên cho form chính là FormMain. 2. Thiết kế giao diện ban đầu cho FormMain như hình vẽ. Khi chạy chương trình, vùng bên tay trái sẽ hiển thị cây thư mục (đối tượng tvwDirs). Mỗi khi chọn 1 thư mục ở cây, nội dung của nó được hiển thị ở lvwItems bên phải. Ngay phía dưới tvwDirs là vùng “preview” nội dung của file được chọn trong lvwItems. Bài tập thực hành Chuyên đề Visual Studio .NET 62
  • 63. 3. Thêm vào Form một đối tượng ImageList có tên là imageList1 với các ảnh như hình dưới: 4. Gán imageList1 cho thuộc tính ImageList của tvwDirs và lvItems. 5. Viết mã lệnh cho sự kiện Load của FormMain như (1) Bài tập thực hành Chuyên đề Visual Studio .NET 63
  • 64. Trước hết, lấy danh sách các ổ đĩa của hệ thống. Ứng với mỗi đĩa như vậy, ta sẽ thêm tất cả các thư mục con cấp 1 của ổ đĩa (hãy giải thích?) bằng cách gọi phương thức (2). Để ý rằng (2) sẽ được gọi đệ quy. 6. Khi người dùng nhấn vào dấu “+” của một node trên treeview để mở nhánh này ra, ta phải hiển thị được tất cả các thư mục con của nó. Việc này được chuẩn bị qua phương thức xử lý sự kiện BeforeExpand của treeview tvwDirs: 7. Khi người sử dụng chọn một node trên cây thì nội dung của thư mục tương ứng (bao gồm tất cả thư mục và files chứa trong nó) sẽ được hiển thị trong listview lvItems. Để thuận tiện cho những thao tác khác sau này của ứng dụng, chúng ta quy ước, trong lvItems, phần tử đại diện cho file sẽ có ImageIndex là 2, còn phần tử đại diện cho thư mục sẽ có ImageIndex là 0. Chúng ta xử lý sự kiện AfterSelect của tvwDirs như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 64
  • 65. Chú ý rằng, ngoài các thư mục và tập tin, ta thêm vào một “thư mục” đặc biệt, thư mục “..” đại diện cho thư mục cha của thư mục hiện tại. 8. Tiếp đến, chúng ta viết mã cho sự kiện nhấn đúp chuột vào một phần tử trong listview. Có 3 khả năng xảy ra: (1) người sử dụng nhấn đúp vào phần tử là thư mục “..”  chuyển đến thư mục trên cấp của thư mục hiện tại (2) người sử dụng nhấn đúp vào phần tử thư mục bình thường  chọn thư mục đó làm thư mục hiện tại (3) người sử dụng nhấn đúp vào phần tử là một file  hiển thị nội dung của file này ra một cửa sổ riêng một cách thích hợp theo nội dung của nó. Trong phần code dưới đây, ta chỉ quan tâm đến 2 khả năng đầu. Khả năng còn lại sẽ được hoàn thiện ở các bước sau. Bài tập thực hành Chuyên đề Visual Studio .NET 65
  • 66. 9. Tiếp theo, ta sẽ cài đặt thêm chức năng Preview nội dung của phần tử file ảnh đang được chọn trong listview lvItems. Sự kiện SelectedIndexChanged của lvItems được xử lý như sau: 10. Phần còn lại của bài thực hành sẽ hướng dẫn bạn cài đặt phần xử lý khả năng (3) trong bước 8. Giả sử rằng, ứng dụng của chúng ta chỉ hỗ trợ hiển thị ra cửa sổ 2 loại nội dung: ảnh và văn bản. Như thế, để hiển thị mỗi loại nội dung, chúng ta cần 1 loại form khác nhau. Tuy thế, cả hai loại form này cũng có một số thuộc tính và hành vi chung. Ta xác định mối quan hệ của chúng qua sơ đồ lớp như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 66
  • 67. Tạo mới một Form để hiển thị nội dung file như sau: 11. Thiết kế giao diện cho FormFile như sau: o Đặt một Panel tên pnlButtons neo phía dưới Form (Dock = Bottom). o Trên pnlButtons, đặt nút btnClose vào vị trí thích hợp. o Có thể trang trí thêm cho pnlButtons, ví dụ đặt một Label trong nó như hình minh họa. o Phần trống còn lại của form dự kiến sẽ dùng để hiển thị nội dung của file. Bài tập thực hành Chuyên đề Visual Studio .NET 67
  • 68. 12. Cài đặt mã lệnh cho form như sau o Cài đặt phương thức xử lý sự kiện Click của nút btnClose (1) o Bổ sung thêm hàm virtual showContent() vào lớp (2). Dự kiến hàm này sẽ làm nhiệm vụ hiển thị nội dung của file: tùy theo file ảnh hay file văn bản mà có cách hiển thị khác nhau, nhưng thao tác chung nhất là hiển thị đường dẫn của file lên tiêu đề cửa sổ. 13. Lưu và Build ứng dụng. (rất quan trọng cho các bước sau!) 14. Bây giờ, ta bổ sung thêm một Windows Form vào project. Tuy nhiên khác với các lớp cửa sổ ta đã bổ sung trước đó, lần này ta sẽ bổ sung một Windows Form kế thừa từ một lớp ta đã thiết kế. Cụ thể là FormFileImage kế thừa từ FormFile. Bài tập thực hành Chuyên đề Visual Studio .NET 68
  • 69. 15. Visual Studio sẽ hiển thị hộp thoại hỏi lớp mà FormFileImage được kế thừa: Chọn FormFile làm Form để kế thừa rồi nhấn OK 16. FormFileImage được tạo ra với giao diện như dưới đây: Bài tập thực hành Chuyên đề Visual Studio .NET 69
  • 70. Để ý, các thành phần được kế thừa sẽ từ lớp cha có mũi tên nhỏ bên góc trái. Hơn nữa, những thành phần thuộc loại Private thì sẽ có thêm biểu tượng ổ khóa, bạn không thể thay đổi thuộc tính của nó trong lớp kế thừa. Ví dụ, nếu muốn chỉnh sửa thuộc tính Text của nút btnClose trong FormFileImage, bạn phải thiết lập btnClose ở FormFile thuộc loại Protected hoặc Public. 17. Bây giờ, bổ sung thêm một PictureBox có tên là pic vào FormFileImage. Thiết lập thuộc tính Dock của nó là Fill. Xem hình minh họa 18. Trong phần mã lệnh cho FormFileImage, ta override hàm showContent như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 70
  • 71. 19. Tương tự như các bước tạo FormFileImage, ta tạo thêm một Windows Form có tên là FormFileText kế thừa từ FormFile có giao diện và phần cài đặt mã lệnh như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 71
  • 72. 20. Cuối cùng, ta hoàn chỉnh chức năng (3) đã mô tả ở Bước 8 bẳng cách sửa lại phương thức xử lý sự kiện DoubleClick của lvItems như sau: Mở rộng - Bổ sung thêm chức năng preview cho file văn bản - Cài đặt chức năng cho các menu Bài tập thực hành Chuyên đề Visual Studio .NET 72
  • 73. PHẦN 3 XỬ LÝ DỮ LIỆU VỚI ADO.NET Xử lý dữ liệu là nhiệm vụ phổ biến và quan trọng của nhiều chương trình ứng dụng. Dữ liệu được truy xuất, xử lý của một chương trình ứng dụng có thể là một tập tin văn bản, tập tin các bản ghi, hay là một nguồn dữ liệu từ CSDL nào đó. .NET Framework cung cấp một lượng lớn các thành phần giao diện (Win Forms, Web Forms) hỗ trợ cho việc trình bày, kết buộc (bind) dữ liệu. Cùng với đó là nền tảng xử lý dữ liệu ADO.NET cung cấp cách thức làm việc với nhiều loại nguồn dữ liệu khác nhau một cách linh động. Do tính chất quan trọng của việc xử lý dữ liệu trong một ứng dụng cùng với sự phức tạp của ADO.NET, trước khi bắt tay vào thực hiện các bài tập thực hành, chúng ta khảo sát qua một số điểm lý thuyết cơ bản. Kiến thức cơ bản về ADO.NET 2.0 3.1 Kiến trúc tổng quan của ADO.NET Kiến trúc của ADO.NET được mô tả như hình dưới, bao gồm hai thành phần chính: Thành phần truy cập nguồn dữ liệu và thành phần lưu trữ xử lý dữ liệu. Thành phần thứ nhất:.NET Framework Data Provider được thiết kế để thực hiện các thao tác kết nối, gửi các lệnh xử lý đến CSDL (thành phần này còn được gọi với một tên khác là lớp kết nối – Connectectivity Layer). Trong ADO.NET, có 4 đối tượng chính với các chức năng cơ bản như sau: • Connection: giúp thực hiện kết nối đến các CSDL Bài tập thực hành Chuyên đề Visual Studio .NET 73
  • 74. • Command: giúp truy cập đến CSDL và thực hiện các phát biểu SQL hay thủ tục lưu trữ sẵn (stored procedure) của CSDL • DataReader: dùng để đọc nhanh nguồn dữ liệu, chỉ được duyệt tuần tự theo chiều tiến của các record • DataAdapter: dùng để chuyển dữ liệu truy vấn được cho các đối tượng lưu trữ và xử lý (DataSet, DataTable). DataAdapter chủ yếu thực hiện các thao tác như SELECT, INSERT, UPDATE, DELETE Về mặt thực chất, thành phần .NET Framework Data Provider cung cấp giao diện lập trình chung để làm việc với các nguồn dữ liệu. Mỗi nhà cung cấp 3 đặc thù sẽ đưa ra một loại data provider riêng. Dưới đây là bảng mô tả giao diện lập trình cùng với các lớp cơ bản của các data provider mà ADO.NET cung cấp sẵn: Interface SQL Server Oracle Provider OLEDB Provider ODBC Provider Provider IDbConnection SqlConnection OracleConnection OledbConnection OdbcConnection IDbDataAdapter SqlDataAdapter OracleDataAdapter OledbDataAdapter OdbcDataAdapter IDbCommand SqlCommand OracleCommand OledbCommand OdbcCommand IDbDataReader SqlDataReader OracleDataReader OledbDataReader OdbcDataReader Để sử dụng data provider nào, chúng ta phải tiến hành khái báo using namspace tương ứng. Chẳng hạn, using System.Data.SqlClient; Ngoài những data provider mô tả ở bảng trên, chúng ta có thể reference đến các data provider khác không được tích hợp sẵn bởi ADO.NET trong Visual Studio .NET, chẳng hạn như data provider dùng cho MySQL, Postgre, … Thành phần thứ hai trong kiến trúc ADO.NET – DataSet – được xem như là container dùng để lưu trữ đối tượng liên quan đến dữ liệu như DataTable, DataRelation, DataView. Thành phần này còn được gọi là lớp không kết nối (disconected layer). DataSet như là một CSDL thu nhỏ tại máy client, có thể chứa các đối tượng table, view, constaint, ralationship giữa các table, … Tất cả dữ liệu từ nguồn dữ liệu thực sẽ được nạp vào DataSet dưới dạng các DataTable, đó là một snapshot của nguồn dữ liệu thực. Khối dữ liệu này sẽ được chỉnh sửa độc lập, sau đó nếu cần sẽ được cập nhật trở lại nguồn dữ liệu thực. Theo nguyên tắc này, chúng ta không cần duy trì kết nối liên tục một cách không cần thiết với nguồn dữ liệu thực trong suốt quá trình thao tác với nó. 3 Nhà cung cấp ở đây được hiểu theo nghĩa cả về loại nguồn dữ liệu lẫn cách thức truy xuất nguồn dữ liệu. Ví dụ, ngoài data provider SqlClient do Microsoft cung cấp, cũng có thể có một tổ chức khác phát triển một provider khác để truy xuất loại nguồn dữ liệu này. Bài tập thực hành Chuyên đề Visual Studio .NET 74
  • 75. 3.2 Tổng quan về các mô hình xử lý dữ liệu trong ADO.NET: Mô hình Kết nối (Connected Model) và Mô hình Ngắt Kết nối (Disconnected Model) 3.2.1 Mô hình Kết nối Trong mô hình kết nối của ADO.NET, có một connection hoạt động được duy trì giữa đối tượng DataReader của ứng dụng và một data source (nguồn dữ liệu). Một dòng dữ liệu (data row) được trả về từ data source mỗi khi phương thức Read của đối tượng DataReader được thực thi. Điểm quan trọng nhất của mô hình kết nối đó là dữ liệu được lấy từ tập dữ liệu (các record được trả về bởi một lệnh SQL nào đó) theo kiểu từng record một cho một lần đọc, chỉ đọc (read-only), và chỉ theo một hướng tiến (forward-only). Hình dưới đây mô tả cách sử dụng DataReader trong chế độ kết nối. Các bước điển hình để làm việc với đối tượng DataReader là như sau: 1. Tạo đối tượng Connection bằng cách truyền một chuỗi Connection string cho hàm khởi dựng của nó. 2. Khởi tạo một biến chuỗi và gán cho câu lệnh SQL dựa theo dữ liệu muốn nạp về. 3. Khởi tạo một đối tượng Command từ với nội dung câu lệnh SQL đã xác định ở trên. 4. Tạo đối tượng DataReader bằng cách thực thi phương thức Command.ExecuteReader(). Đối tượng này sau đó sẽ được dùng để đọc kết quả của câu truy vấn mỗi dòng một lần. Đoạn code sau minh họa các bước trên với Data Provider SqlClient. Đoạn code sẽ đọc danh sách họ tên các sinh viên trong một bảng SinhVien của cơ sở dữ liệu và hiển thị lên một điều khiển ListBox. Chi tiết về các đối tượng DataReader, Command, Connection sẽ được đề cập chi tiết sau. using System.Data.SqlClient; ... // (1) Tao Connection SqlConnection cn = new SqlConnection(chuoiKetNoi); cn.Open(); Bài tập thực hành Chuyên đề Visual Studio .NET 75
  • 76. // (2) Chuoi SQL thuc hien lay danh sach ten cac sinh vien xep tang dan theo NgaySinh string sql = "SELECT HoTen FROM SinhVien ORDER BY NgaySinh"; // (3) Tao doi tuong Command SqlCommand cmd = new SqlCommand(sql, conn); DbDataReader rdr; // (4) Tao doi tuong DataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection); while (rdr.Read()) listBox1.Items.Add(rdr["HoTen"]); // Fill ListBox rdr.Close(); // Dong datareader sau khi da su dung xong Tham số được sử dụng trong phương thức ExecuteReader xác định đối tượng Connection sẽ được đóng sau khi DataReader được đóng. 3.2.2 Mô hình Ngắt Kết nối Triết lý của mô hình Ngắt kết nối đó là: Dữ liệu được nạp – sử dụng một lệnh SQL – từ nguồn dữ liệu bên ngoài vào bộ nhớ đệm tại máy client; tập kết quả được xử lý tại máy cục bộ; mọi cập nhật sau đó sẽ được truyền từ dữ liệu trong bộ nhớ ngược trở lại nguồn dữ liệu. Mô hình được gọi là “ngắt kết nối” bởi vì đối tượng kết nối chỉ được mở đủ lâu để đọc dữ liệu từ nguồn dữ liệu và tiến hành các thao tác cập nhật. Bằng cách đưa dữ liệu về phía máy client, tài nguyên của server – chẳng hạn như thông tin dữ liệu Connection, bộ nhớ, thời gian xử lý – sẽ được giải phóng bớt. Tuy vậy, mô hình này cũng có nhược điểm về thời gian cần để nạp tập dữ liệu và bộ nhớ dùng để chứa dữ liệu tại máy client. Như hình dưới đây minh họa, các thành phần chính của mô hình ngắt kết nối đó là DataApdapter và DataSet. DataAdapter làm nhiệm vụ như là cầu nối giữa nguồn dữ liệu và DataSet, nạp dữ liệu vào các bảng của DataSet và đẩy các thay đối ngược trở lại nguồn dữ liệu. Một DataSet đóng vai trò như là một cơ sở dữ liệu quan hệ nằm trong bộ nhớ, chứa một hay nhiều DataTables, giữa các DataTable này cũng có thể có các mối quan hệ với nhau như trong một cơ sở dữ liệu quan hệ thực. Một DataTable chứa các dòng và các cột dữ liệu thường được lấy từ cơ sở dữ liệu nguồn. Bài tập thực hành Chuyên đề Visual Studio .NET 76
  • 77. Trong số các phương thức và thuộc tính của DataAdapter thì Fill() và Update() là hai phương thức quan trọng nhất. Fill() chuyển một query đến cơ sở dữ liệu và lưu tập kết quả trả về trong một DataTable nào đó; phương thức Update() thực hiện một thao tác thêm, xóa, cập nhật dựa trên những thay đối của đối tượng DataSet. Các lệnh cập nhật thực sự được chứa trong các thuộc tính của DataAdapter. Chi tiết về DataAdapter sẽ được đề cập ở phần sau. Để minh họa cách thức làm việc với DataAdapter và DataSet, đoạn code dưới đây giới thiệu cách tạo ra một đối tượng DataTable, nạp dữ liệu từ một cơ sở dữ liệu, và đưa nó vào một DataSet. string sql = "SELECT MaSinhVien, HoTen, NgaySinh FROM SinhVien"; string connStr = "Data Source=MYSERVER;Initial Catalog=qlsinhvien; User Id=k28;Password=k28;"; // (1) Tao doi tuong data adapter SqlDataAdapter da = new SqlDataAdapter(sql, connStr); // (2) Tao doi tuong dataset DataSet ds = new DataSet(); // (3) Tao mot Table co ten “SinhVien” trong dataset va nap du lieu cho no da.Fill(ds, "SinhVien"); // (4) Hien thi danh sach ten sinh vien ra list box DataTable dt = ds.Tables["SinhVien"]; for (int i=0; i< dt.Rows.Count;i++) { DataRow row = dt.Rows[i]; listBox1.Items.Add(row["HoTen"]); } Bước đầu tiên là tạo ra một thể hiện của SqlDataAdapter bằng cách truyền một câu lệnh SELECT và chuỗi kết nối cho phương thức khởi dựng của lớp này. DataAdapter sẽ lo đến việc tạo ra đối tượng Connection cũng như việc mở, đóng Connection khi cần thiết. Sau khi một DataSet rỗng sẽ được tạo ra, phương thức Fill() Bài tập thực hành Chuyên đề Visual Studio .NET 77
  • 78. của DataAdapter sẽ tạo ra một DataTable có tên là “SinhVien” trong DataSet và nạp các dòng dữ liệu vào DataTable này (bằng câu lện SQL dạng SELECT của DataAdapter). Mỗi column của DataTable sẽ tương ứng với một column trong bảng của cơ sở dữ liệu nguồn. Dữ liệu trong bảng dữ liệu sau đó được đưa vào một ListBox bằng cách duyệt qua danh sách các dòng của DataTable. 3.3 Làm việc với mô hình Kết nối trong ADO.NET Như đã mô tả tổng quan trong phần trước, mô hình Kết nối được dựa trên việc thiết lập một Connection đến CSDL và sau đó sử dụng các Command để thực hiện việc thêm, xóa, sửa, hay đọc dữ liệu từ data source (nguồn dữ liệu) được kết nối. Đặc điểm phân biệt của mô hình này đó là các Command được phát sinh, làm việc với data source thông qua một Connection đang hoạt động – Connection này sẽ mở cho đến khi các thao tác được hoàn tất. Cho dù là làm việc với mô hình Kết nối hay Ngắt kết nối, bước đầu tiên trong quá trình truy xuất một data source đó là tạo ra một đối tượng Connection để làm đường truyền giữa ứng dụng với data source. 3.3.1 Lớp Connection Có nhiều lớp Connection trong ADO.NET – mỗi lớp tương ứng với một Data Provider – bao gồm SqlConnection, OracleConnection, OleDbConnection, OdbcConnection. Mặc dù mỗi lớp có thể gồm những đặc tính riêng, nhưng các lớp này đều phải implement interface IDbConnection. Bảng dưới đây tóm tắt các thành phần được định nghĩa bởi interface này. Loại Tên Mô tả Property ConnectionString Get/Sets chuỗi kết nối đến data source. Property ConnectionTimeout Khoảng thời gian tối đa tính bằng giây để chờ thực hiện việc kết nối đến data source Property Database Tên CSDL ứng với Connection hiện tại Property State Trạng thái hiện tại của Connection. Trả về một giá trị kiểu liệt kê (enumeration): Broken, Closed, Connecting, Executing, Fetching, hoặc Open Method Open Mở một Connection. Roll back mọi thao tác đang Close làm dở. Đóng Connection – trả Connection cho Connection Pool nếu như có sử dụng Connection Pool Method BeginTransaction Khởi tạo một database transaction Method ChangeDatabase Thay đối CSDL hiện tại cho Connection đang mở. Chuỗi mô tả tên CSDL mới được truyền cho phương thức này Method CreateCommand Tạo ra một đối tượng Command ứng với Connection Bài tập thực hành Chuyên đề Visual Studio .NET 78
  • 79. 3.3.1.1 Connection string Thuộc tính ConnectionString xác định data source và các thông tin cần thiết để truy xuất data source, chẳng hạn như User ID và Password, … Ngoài những thông tin cơ bản này, Connection string còn có thể chứa các giá trị cho các trường dữ liệu đặc trưng cho data provider. Ví dụ, Connection string cho Ms Sql Server có thể chứa các giá trị để quy định Connection Timeout và Packet Size. Dưới đây là các ví dụ về cách thành lập chuỗi kết nối cho các data provider thường gặp. Danh sách đầy đủ về cách thành lập các chuỗi kết nối được cho ở Phụ lục A. • SqlConnection sử dụng cơ chế xác thực kiểu SQL Server: “server=;database=;uid=;pwd=” hoặc “Data Source=;Initial Catalog=;User ID=;Password=” • SqlConnection sử dụng cơ chế xác thực kiểu Windows: “Server=;Database=;Trusted_Connection=yes” Ở đây,  là tên/máy chủ chứa CSDL,  là tên CSDL,  là tên đăng nhập,  là mật khẩu tương ứng. Ví dụ: “server=192.168.0.1;database=qlnhanvien;uid=k28;pwd=spider” hoặc “Server=192.168.0.1;Database=qlnhanvien;Trusted_Connection=yes” • OleDbConnection sử dụng để kết nối CSDL Access phiên bản trước 2003: “Provider=Microsoft.Jet.OLEDB.4.0;DataSource=” Ví dụ: o string stConnection = string.Format(“Provider=Microsoft.Jet.OLEDB.4.0;DataSource={0}”, @”c:program filesqlnhanvien.mdb”); o Sử dụng trong ứng dụng Internet: string stConnection = string.Format(“Provider=Microsoft.Jet.OLEDB.4.0;DataSource={0}”, Server.MapPath(“/data/qlnhanvien.mdb”); • ODBC: “DSN=” với  là Data Source Name (DSN), ví dụ “DSN=qlnhanvien” Các Connection string được dùng để tạo ra đối tượng Connection. Cách thực hiện thông thường là truyền chuỗi này cho hàm khởi dựng như ví dụ dưới đây: Bài tập thực hành Chuyên đề Visual Studio .NET 79
  • 80. string stConnection = "Data Source=192.168.0.1;” + “Initial Catalog=films;” + “User Id=k28;”+ “Password=spider"; SqlConnection cn = new SqlConnection(stConnection); cn.Open(); //Open connection 3.3.1.2 Connection Pooling Tạo một Connection là một quá trình tốn nhiều thời gian – trong một số trường hợp, việc này thậm chí còn tốn thời gian hơn việc thực thi các Command. Để loại bỏ điều này, ADO.NET cung cấp một khái niệm gọi là connection pool. Connection pool quản lý các Connection có trùng Connection string để tối ưu hóa số lần thiết lập, hợp lệ hóa thông tin kết nối. Các quy tắc quy định connection pool cần biết: - Cơ chế Connection pooling được kích hoạt theo mặc định. Cơ chế này được tắt bằng cách thêm vào Connection string “Pooling=false” đối với SqlConnection hoặc “OLE DB Services=-4” đối với OleDbConnection. - Mỗi connection pool được ứng với một connection string duy nhất. Khi có một Connection được yêu cầu, pool handler (bộ quản lý pool) sẽ so sánh connection string với những connection string trong các pools đang tồn tại. Nếu có một Connection trùng khớp thì Connection tương ứng sẽ được xác định trong pool. - Nếu tất cả các connection trong pool đang được sử dụng khi lại có yêu cầu về connection thì yêu cầu đó sẽ được xếp vào hàng đợi cho đến khi có một connection rảnh. Các connection sẽ được giải phóng khi phương thức Close hay Dispose của đối tượng connection được gọi. - Connection pool được đóng khi tất cả các connection trong nó được giải phóng và hết thời gian (time out). Đối với SQL Server, bạn có thể điều khiển hành vi của conneciton pooling bằng cách gộp các cặp key-value vào connection string. Các key này có thể được sử dụng để thiết lập số lượng nhỏ nhất và lớn nhất các connection trong pool cũng như xác định xem một connection có cần phải reset khi nó được lấy từ pool ra hay không. Một key đặc biệt chú ý đó là key có tên Lifetime, xác định thời gian mà connection có thể tồn tại trước khi nó bị hủy bỏ. Giá trị này được kiểm tra khi một connection được trả về cho pool. Nếu connection đã được mở trước đó, và lâu hơn giá trị Lifetime thì nó sẽ bị hủy. Đoạn code dưới đây minh họa các dùng các key này cho SqlClient: Bài tập thực hành Chuyên đề Visual Studio .NET 80
  • 81. string stConnection = "Server=192.168.0.1;” + “Trusted_Connection=yes;” + “database=qlnhanvien;" + "connection reset=false;" + "connection Lifetime=60;" + // Seconds "min pool size=1;" + "max pool size=50"; // Default=100 SqlConnection cn = new SqlConnection(cnString); 3.3.2 Đối tượng Command Sau khi một đối tượng connection được tạo ra, bước tiếp theo trong quá trình truy xuất CSDL – đối với mô hình Kết nối – đó là tạo ra một đối tượng Command để gửi một query (select) hay một action command (thêm, xóa, sửa) đến data source. Có nhiều loại lớp Command ứng với các data provider; các lớp này đều implement interface IDbCommand. 3.3.2.1 Tạo đối tượng Command Bạn có thể dùng một trong nhiều hàm khởi dựng để tạo đối tượng Command một cách trực tiếp hoặc sử dụng cách tiếp cận ProviderFactory. Đoạn code dưới đây minh họa các tạo ra một đối tượng Command và thiết lập các thuộc tính của nó. SqlConnection conn = new SqlConnection(connstr); conn.open(); string sql = "INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES (@pMaSinhVien, @pHoTen)"; SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.commandText = sql; cmd.Parameters.AddWithValue ("@pMaSinhVien", 12); cmd.Parameters.AddWithValue ("@pHoTen", "tnv spider"); Trong trường hợp ứng dụng có thể phải sử dụng nhiều data provider, bạn nên sử dụng cách tiếp cận provider factory. Factory được tạo ra bằng cách truyền chuỗi data provider cho hàm khởi dựng của nó. Tiếp đến, phương thức CreateCommand được gọi để trả về một đối tượng command. string provider = "System.Data.SqlClient"; Bài tập thực hành Chuyên đề Visual Studio .NET 81
  • 82. DBProviderFactory factory = DbProviderFactories.GetFactory(provider); DbCommand cmd = factory.CreateCommand(); // DbCommand là một lớp trừu tượng cmd.CommandText = sql; // sql là một chuỗi query hay command cmd.Connection = conn; // conn là một Connection 3.3.2.2 Thực thi một Command Lệnh SQL được gán trong thuộc tính CommandText của đối tượng Command sẽ được thực thi bằng một trong các phương thức được chỉ ra ở bảng dưới đây Phương thức Mô tả ExecuteNonQuery Thực thi truy vấn hành động (action query) và trả về số lượng dòng dữ liệu bị ảnh hưởng bởi truy vấn đó: cmd.CommandText = "DELETE SinhVien WHERE MaSinhVien=12"; int soLuong = cmd.ExecuteNonQuery(); ExecuteReader Thực thi một query và trả về đối tượng DataReader để có thể truy cập tập kết quả của query đó. Phương thức này nhận một tham số tùy chọn kiểu CommandBehavior để có thể tăng hiệu năng thực thi query. cmd.CommandText = "SELECT * FROM SinhVien” + “WHERE YEAR(NgaySinh) > 1981”; SqlDataReader rdr= cmd.ExecuteReader(); ExecuteScalar Thực thi một query và trả về giá trị của cột đầu tiên trong dòng đầu tiên của tập kết quả. cmd.CommandText="SELECT COUNT(MaSinhVien) FROM SinhVien"; int soSinhVien = (int)cmd.ExecuteScalar(); ExecuteXmlReader Chỉ có cho data provider SQL Server. Trả về một đối tượng XmlReader dùng để truy xuất tập dữ liệu. Tham khảo thông tin về XmlReader trong MSDN Bài tập thực hành Chuyên đề Visual Studio .NET 82
  • 83. ExecuteReader là phương thức quan trọng nhất trong các phương thức kể trên. Phương thức này trả về một đối tượng DataReader giúp truy xuất đến các dòng dữ liệu trả về bởi query. Xem ví dụ dưới đây: dr = cmd.ExecuteReader(sql, ); Ở đây,  là một giá trị kiểu CommandBehavior để chỉ định behavior (hành vi) thực thi của query. Một số data providers sử dụng  để tối ưu quá trình thực thi query. Danh sách các giá trị và tác dụng của tham số  được mô tả chi tiết như dưới đây: • SingleRow. Chỉ định rằng query chỉ trả về 1 dòng dữ liệu. Behavior mặc định là trả về nhiều tập kết quả. • SingleResult. Query trả về một giá trị tuyến tính đơn nhất (single scalar value). • KeyInfo. Trả về thông tin về column và primary key. Behavior này được sử dụng với phương thức GetSchema của DataReader để lấy thông tin về các column trong lược đồ (schema). • SchemaOnly. Dùng để lấy về tên của các cột trong tập dữ liệu trả về: Ví dụ dr = cmd.ExecuteReader(CommandBehavior.SchemaOnly); string col1 = dr.GetName(0); // tên cột đầu tiên • SequentialAccess. Cho phép dữ liệu trong dòng trả về có thể được truy xuất tuần tự theo column. Behavior này được dùng cho các trường dữ liệu BLOB hay TEXT. • CloseConnection. Đóng connection khi DataReader được đóng. 3.3.2.3 Thực thi Stored Procedure (thủ tục lưu trữ sẵn) với đối tượng Command Một stored procedure là một đoạn code SQL được lưu sẵn trong CSDL và có thể được thực thi như là một script. ADO.NET hỗ trợ việc thực thi các stored procedure cho các data provider OleDb , SqlClient, ODBC, và OracleClient. Các bước để thực thi một stored procedure: - Thiết lập thuộc tính SqlCommand.CommandText thành tên của procedure; - Thiết lập thuộc tính CommandType là CommandType.StoredProcedure; - Thiết lập các Parameter (nếu có) cho procedure - Thực thi phương thức ExecuteNonQuery. Thủ tục dưới đây cho phép các mẫu tin lấy về từ bảng SinhVien được phân thành từng nhóm các trang, mỗi trang 10 record. Đầu vào của của procedure là @pTrang (số hiệu Bài tập thực hành Chuyên đề Visual Studio .NET 83
  • 84. trang cần lấy); đầu ra của procedure là số trang tổng cộng của tập dữ liệu. Đoạn code minh họa phía dưới thực hiện việc thiết lập để lấy về trang dữ liệu đầu tiên. SqlCommand cmd = new SqlCommand(); cmd.CommandText = "spListSinhVien"; // tên procedure cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add(“@pTrang", SqlDbType.Int); cmd.Parameters.Add(“@pTongSoTrang", SqlDbType.Int); cmd.Parameters[0].Direction= ParameterDirection.Input; cmd.Parameters[0].Value= 1; // thiết lập để lấy về trang đầu tiên cmd.Parameters[1].Direction=ParameterDirection.Output; cmd.CommandTimeout=10; // Cho command tối đa 10s để thực thi SqlDataReader dr = cmd.ExecuteReader(); while (dr.Read()) { // xử lý tập dữ liệu ở đây } dr.Close(); // DataReader phải được đóng trước khi đọc tham số đầu ra int tongSoTrang = cmd.Parameters[1].Value; Ví dụ này sử dụng data provider SqlClient. Có thể chỉnh sửa một phần nhỏ thì nó cũng có thể hoạt động với OleDb. Điểm khác biệt mấu chốt giữa SqlClient và OleDb đó là cách quản lý các parameter. SqlClient yêu cầu tên parameter phải đúng với tên parameter của stored procedure; trong khi đó OleDb lại truyền các parameter cho stored procedure dựa vào vị trí, vì vậy tên parameter là không quan trọng. Nếu procedure trả về giá trị kết quả, OleDb phải thiết kế để parameter đầu tiên trong danh sách làm nhiệm vụ này. Với SqlClient, chúng ta chỉ cần thêm một parameter với một tên nào đó và xác định hướng trả về (direction) của parameter này là Return Value. Phần code của stored procedure là như sau: CREATE PROCEDURE spListSinhVien @pTrang int, @pTongSoTrang int output Bài tập thực hành Chuyên đề Visual Studio .NET 84
  • 85. AS /* Thủ tục trả về một tập kết quả gồm các SinhVien xếp theo HoTen. Tập kết quả được phân thành các trang, mỗi trang 10 SinhVien. */ SET NOCOUNT ON SELECT @pSoTrang = CEILING(COUNT(*)/10) FROM SinhVien if @pTrang = 1 or @pTrang <1 begin SELECT TOP MaSinhVien, HoTen FROM SinhVien ORDER BY HoTen set @pTrang = 1 return 0 end if @pTrang > @pTongSoTrang begin SET @pTrang = @pTongSoTrang declare @RowCount int set @RowCount = (@pTrang * 10) exec ( 'SELECT * FROM ( SELECT TOP 10 a.* FROM ( SELECT TOP ' + @RowCount + ' * FROM SinhVien ORDER BY HoTen )a ORDER BY HoTen desc )b ORDER BY HoTen' ) return 0 end Bài tập thực hành Chuyên đề Visual Studio .NET 85
  • 86. 3.3.2.4 Sử dụng Parameter trong các Command không phải là Stored Procedures Trong các query được thành lập trực tiếp (chứ không phải là stored procedure như ở trên), chúng ta cũng có thể sử dụng các Parameter. Ví dụ dưới đây minh họa cách thức bổ sung một record vào bảng SinhVien: string sql = "INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES (@pMaSinhVien, @pHoTen)"; SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.commandText = sql; cmd.Parameters.AddWithValue("@pMaSinhVien", 12); cmd.Parameters.AddWithValue("@pHoTen", "tnv spider"); Một cách khác để thực hiện việc bổ sung record như trên là sử dụng phép nối chuỗi4 như thế này: int iMaSinhVien = 12; string stHoTen = "tnv spider"; sql = string.Format(“INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES ({0}, ‘{1}’)”, iMaSinhVien, stHoTen); SqlCommand cmd = new SqlCommand(sql, conn); 3.3.3 Đối tượng DataReader Như đã thấy trong các ví dụ trước, một DataReader cho phép lấy các dòng và cột dữ liệu của dữ liệu trả về khi thực thi một query. Việc truy xuất dòng được định nghĩa bởi interface IDataRecord. Dưới đây là các member quan trọng của interface này. 3.3.3.1 Truy xuất các dòng dữ liệu với DataReader DataReader lấy về từng dòng đơn (single row) từ một tập dữ liệu trả về mỗi khi phương thức Read của nó được thực thi. Nếu không có dòng dữ liệu nào thì phương thức này trả về giá trị false. DataReader phải được đóng sau khi các thao tác xử lý các dòng được hoàn tất để giải phóng tài nguyên hệ thống. Bạn có thể sử dụng thuộc tính DataReader.IsClosed để biết được DataReader đã được đóng hay chưa. 4 Trong thực tế, giải pháp nối chuỗi ít khi được sử dụng vì lý do an toàn dữ liệu. Hãy hình dung trong đoạn code này, nếu stHoTen được gán giá trị là “tnv spider’); DELETE * FROM SinhVien;--”. Khi đó query được thực thi sẽ là “INSERT INTO SinhVien (MaSinhVien, HoTen) VALUES (12, ‘tnv spider’); DELETE * FROM SinhVien;--)” Lỗ hổng kiểu này thường được gọi với tên SQL Injection. Bài tập thực hành Chuyên đề Visual Studio .NET 86
  • 87. Mặc dù DataReader là ứng với một Command đơn, nhưng Command này lại có thể chứa nhiều query trong đó, do đó có thể trả về nhiều tập dữ liệu kết quả. Đoạn code dưới đây minh họa cách xử lý các dòng dữ liệu trả về bởi 2 query trong một Command. string q1 = "SELECT * FROM SinhVien WHERE YEAR(NgaySinh) < 1981"; string q2 = "SELECT * FROM SinhVien WHERE YEAR(NgaySinh) > 1990"; cmd.CommandText = q1 + ";" + q2; // hai query được ngăn cách nhau bởi dấu ; DbDataReader rdr = cmd.ExecuteReader(); bool readNext = true; while (readNext) { while (rdr.Read()) MessageBox.Show(rdr.GetString(1)); readNext = rdr.NextResult(); // kiem tra xem con tap du lieu nao nua khong } rdr.Close(); conn.Close(); DataReader không có thuộc tính hay phương thức nào cho biết số lượng dòng dữ liệu trả về trong tập dữ liệu của nó (do đặc tính forward-only của DataReader), tuy nhiên, chúng ta có thể sử dụng thuộc tính HasRows (kiểu Boolean) của DataReader để xác định xem nó có 1 hay nhiều dòng để đọc hay không. 3.3.3.2 Truy xuất giá trị của column Có nhiều cách để truy xuất dữ liệu chứa trong các columns của dòng hiện tại của DataReader: - Truy xuất như là một array dùng số thứ tự column (bắt đầu từ 0) hoặc dùng tên column - Sử dụng phương thức GetValue bằng cách truyền cho phương thức này số thứ tự của column - Sử dụng một trong các phương thức định kiểu GetXXX, bao gồm GetString, GetInt32, GetDateTime, GetDouble, … Đoạn code dưới đây minh họa các thức truy xuất giá trị dữ liệu của các column. Bài tập thực hành Chuyên đề Visual Studio .NET 87
  • 88. cmd.CommandText = "SELECT MaSinhVien, Hoten, GioiTinh, NgaySinh FROM SinhVien ” + “WHERE YEAR(NgaySinh) = 1981"; dr = cmd.ExecuteReader(); dr.Read(); // Các cách để lấy dữ liệu kiểu string ở cột thứ 2 (HoTen) string stHoTen; stHoTen = dr.GetString(1); stHoTen = (string)dr.GetSqlString(1); // SqlClient provider stHoTen = (string)dr.GetValue(1); stHoTen = (string)dr["HoTen"]; stHoTen = (string)dr[1]; // Lấy dữ liệu kiểu DateTime ở cột thứ 4 (NgaySinh) có kiểm tra giá trị NULL if (!dr.IsDbNull(3)) DateTime dtNgaySinh = dr.GetDateTime(3); Phương thức GetString có điểm thuận lợi trong việc ánh xạ nội dung dữ liệu từ CSDL sang kiểu dữ liệu của .NET. Các cách tiếp cận khác đều trả về các kiểu đối tượng có yêu cầu phép chuyển kiểu. Vì lý do này, bạn nên sử dụng các phương thức GetXXX cho các kiểu dữ liệu xác định. Cũng lưu ý rằng, phương thức GetString không yêu cầu phép chuyển kiểu, nhưng bản thân nó không thực hiện bất cứ phép chuyển đổi nào; chính vì thế, nếu dữ liệu là không đúng như kiểu dữ liệu trông đợi sẽ có Exception được trả ra. Nhiều ứng dụng phụ thuộc vào tầng xử lý dữ liệu để cung cấp DataReader. Với những trường hợp như thế, ứng dụng có thể sử dụng metadata (siêu dữ liệu) để xác định tên column, kiểu dữ liệu của column, và các thông tin khác về column. Đoạn code sau đây minh họa việc in ra danh sách các tên và kiểu dữ liệu của các column mà đối tượng DataReader đang quản lý: // In ra danh sách các tên column của một đối tượng DataReader có tên dr for (int i = 0; i < dr.FieldCount; i++) Console.WriteLine(“Column {0} co kieu du lieu {1}”, dr.GetName(i), dr.GetDataTypeName(i)); // Column name Bài tập thực hành Chuyên đề Visual Studio .NET 88
  • 89. Có một cách khác toàn diện hơn để quản lý toàn bộ thông tin về lược đồ (schema) của tập dữ liệu kết quả trả về, đó là sử dụng phương thức GetSchemaTable. Phương thức này trả về một đối tượng DataTable mà mỗi dòng trong DataTable này sẽ biểu diễn một column trong tập dữ liệu kết quả. Đoạn code dưới đây minh họa cách truy xuất tất cả các thông tin về các column của một tập dữ liệu trả về. DataTable schemaTable = dr.GetSchemaTable(); int stt = 0; foreach (DataRow r in schemaTable.Rows) { foreach (DataColumn c in schemaTable.Columns) { Console.WriteLine(stt.ToString() + " " + c.ColumnName + ": " + r[c]); stt++; } } Kết quả hiển thị: 0 ColumnName: movie_ID 1 ColumnOrdinal: 0 … //không liệt kê 12 DataType: System.Int32 … //không liệt kê 3.3.4 Ví dụ Giả sử ta đã có cơ sở dữ liệu quanlythuvien trong SQL Server có quan hệ như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 89
  • 90. Ví dụ về đối tượng Connection, Command và DataReader Thiết kế Form để tạo mới 1 tài khoản như sau (làm việc trên bảng nhanvien): ListView1 Frmtaomoitk sử dụng các trường, phương thức và sự kiện sau: Các điều khiển Tên điều khiển Thuộc tính Bài tập thực hành Chuyên đề Visual Studio .NET 90
  • 91. Form Name: Frmtaomoitk Text:Tạo mới một tài khoản sử dụng chương trình listView Name:listView1 Columns: Add thêm 4 cột: Họ tên, Địa chỉ, Tên đăng nhập và Quyền hạn View: Details GridLines:True groupBox Name:groupBox1 Text: Thông tin cơ bản Label Tạo ra 5 label để hiển thị: Mã nhân viên, Họ tên, Địa chỉ, Tên đăng nhập và quyền hạn. TextBox Tạo ra 4 TextBox lần lượt với các tên: txtmanv, txthoten, txtdiachi, txttendangnhap Button Tạo 8 button lần lượt với các tên butdau, butlui, buttien, butcuoi, buttaomoi, buttimkiem, butxoabo,butthoat Các trường: Tên trường Ý nghĩa Cn Dùng để kết nối đến cơ sở dữ liệu quanlythuvien cmdSelect sqlCommand sử dụng câu lệnh select để hiển thị và tìm kiếm cmdInsert sqlCommand sử dụng câu lệnh Insert để tạo thêm 1 tài khoản cmdXoa sqlCommand sử dụng câu lệnh Delete để xóa 1 tài khoản I Tài khoản thứ i Các phương thức + Hàm dựng Frmtaomoitk để tạo giao diện public Frmtaomoitk() { InitializeComponent(); } + Phương thức Moketnoi(): Kiểm tra đường kết nối, nếu đang mở thì đóng lại, sau đó mở lại đường kết nối private void Moketnoi() { if (cn.State == ConnectionState.Open) cn.Close(); cn.Open(); } + Phương thức LoadListView: Lấy dữ liệu của bảng nhanvien nạp dữ liệu lên listView1. Phương thức này được gọi khi thay đổi dữ liệu trong bảng nhận viên như nhâp thêm hoặc xóa đi 1 nhân viên. Sử dụng 2 đối tượng SqlCommand, SqlDataReader private void LoadListView() Bài tập thực hành Chuyên đề Visual Studio .NET 91
  • 92. { Moketnoi(); cmdSelect = new SqlCommand("select * from nhanvien", cn); SqlDataReader r = cmdSelect.ExecuteReader(); listView1.Items.Clear(); // Xóa tất cả dữ liệu trong listView1 while (r.Read()) { string[] st = new string[5]; st[0] = r[0].ToString(); st[1] = r[1].ToString(); st[2] = r[2].ToString();// Không hiển thị mật khẩu, nên không có r[3] st[3] = r[4].ToString(); st[4] = r[5].ToString(); ListViewItem lv = new ListViewItem(st); listView1.Items.Add(lv); } cmdSelect.Dispose(); } + Phương thức LoadItem: Lấy dữ liệu từ dòng thứ i của listView1 đưa vào txtmanv, txthoten, txtdiachi, txttendangnhap và comboBox1. Phương thức này được gọi khi di chuyển qua lại thông tin của các nhân viên. private void LoadItem(int i) { txtmanv.Text = listView1.Items[i].Text; txthoten.Text = listView1.Items[i].SubItems[1].Text; txtdiachi.Text = listView1.Items[i].SubItems[2].Text; txttendangnhap.Text = listView1.Items[i].SubItems[3].Text; comboBox1.Text = listView1.Items[i].SubItems[4].Text; } + Phương thức LoadCombox: Đưa dữ liệu vào cho comboBox1. Giả sử chỉ có 3 quyền hạn: admin, sinhvien và Thuthu. Phương thức này được gọi khi vừa nạp Form lên private void LoadCombox() { comboBox1.Items.Add("Admin"); comboBox1.Items.Add("Sinhvien"); comboBox1.Items.Add("ThuThu"); comboBox1.Text = "Admin"; } Bài tập thực hành Chuyên đề Visual Studio .NET 92
  • 93. + Phương thức XoaTextBox: Xóa hết dữ liệu trong các textBox, phương thức này được goi khi nhập thêm 1 tài khoản. private void XoaTextBox() { txtmanv.Clear(); txthoten.Clear(); txtdiachi.Clear(); txttendangnhap.Clear(); txtmanv.Focus(); } + Phương thức KiemTraMa: Kiểm tra xem có mã nhân viên nào bằng với ma hay không. Phương thức này được gọi khi nhập thêm 1 tài khoản private int KiemTraMa(string ma) { Moketnoi(); cmdSelect = new SqlCommand("select count(*) from nhanvien where manhanvien='"+ma.Trim()+"'"); cmdSelect.Connection = cn; return (int)cmdSelect.ExecuteScalar(); } + Sự kiện Frmtaomoitk_Load: Tạo và mở ra đường kết nối đến cơ sở dữ liệu quanlythuvien, tên máy chủ nhha, sử dụng cơ chế xác thực kiểu Windows, nạo dữ liệu vào cho các điều khiển. private void Frmtaomoitk_Load(object sender, EventArgs e) { try { cn = new SqlConnection("Data Source=nhha;Initial Catalog=quanlythuvien; Trusted_Connection=yes"); cn.Open(); } catch (Exception loi) { MessageBox.Show("Không thể kết nối được"); } LoadListView(); //Nạp dữ liệu vào cho listView1 i = 0; LoadItem(i);// Nạp dữ liệu vào cho các textBox và comboBox1 LoadCombox(); } + Sự kiện butdau_Click: Nạp dữ liệu của dòng đầu tiên từ listView1 vào cho các textBox và comboBox private void butdau_Click(object sender, EventArgs e) Bài tập thực hành Chuyên đề Visual Studio .NET 93
  • 94. { i = 0; LoadItem(i); } + Sự kiện buttien_Click: Nạp dữ liệu của dòng tiếp theo từ listView1 vào cho các textBox và comboBox private void buttien_Click(object sender, EventArgs e) { i++; if (i == listView1.Items.Count) i = listView1.Items.Count - 1; LoadItem(i); } + Sự kiện butlui_Click: private void butlui_Click(object sender, EventArgs e) { i--; if (i < 0) i = 0; LoadItem(i); } + Sự kiện butcuoi_Click: private void butcuoi_Click(object sender, EventArgs e) { i = listView1.Items.Count - 1; LoadItem(i); } + Sự kiện butTaomoi_Click: Được sử dụng để thêm 1 tài khoản (1 nhân viên), nút butTaomoi có 2 trạng thái tạo mới và lưu. Nếu người sử dụng kích vào nút Tạo mới sẽ chuyển sang trạng thái là lưu và ngược lại. private void butTaomoi_Click(object sender, EventArgs e) { if (butTaomoi.Text.Equals("Tạo mới")) { XoaTextBox(); butTaomoi.Text = "Luu"; } else // Kiểm tra xem mã nhân viên này có hay chưa ? if (KiemTraMa(txtmanv.Text)==1) { MessageBox.Show("Mã này đã có"); Bài tập thực hành Chuyên đề Visual Studio .NET 94
  • 95. txtmanv.Clear(); txtmanv.Focus(); } else { string ma = txtmanv.Text; string hoten = txthoten.Text; string diachi = txtdiachi.Text; string tendangnhap = txttendangnhap.Text; string matkhau = "";// Khi tạo 1 tài khoản thì mật khẩu ban đầu là rỗng string quyenhan = comboBox1.Text; Moketnoi(); string sql="insert into nhanvien values("+"'"+ma+"','"+hoten+"','"+diachi +"','" +tendangnhap+"','"+matkhau+"','"+quyenhan +"')"; cmdInsert = new SqlCommand(sql,cn); cmdInsert.ExecuteNonQuery(); MessageBox.Show("Đã lưu thành công"); LoadListView(); //Nạp lại dữ liệu mới vào listView1 butTaomoi.Text = "Tạo mới"; cmdInsert.Dispose(); } } + Sự kiện buttimkiem_Click: Khi người sử dụng gõ 1 mã nhân viên vào txtmanv và kích vào nút buttimkiem, nếu tìm thấy mã nhân viên này sẽ hiển thị kết quả lên các textBox và comboBox private void buttimkiem_Click(object sender, EventArgs e) { Moketnoi(); string sql = "select * from nhanvien where manhanvien='" + txtmanv.Text + "'"; cmdSelect = new SqlCommand(sql,cn); SqlDataReader dr = cmdSelect.ExecuteReader(); if (dr.Read())// Đã tìm thấy { txtmanv.Text = dr[0].ToString(); txthoten.Text = dr[1].ToString(); txtdiachi.Text = dr[2].ToString(); txttendangnhap.Text = dr[4].ToString(); comboBox1.Text = dr[5].ToString(); } else MessageBox.Show("Không tìm thấy"); Bài tập thực hành Chuyên đề Visual Studio .NET 95
  • 96. } + Sự kiện butXoabo_Click: Xóa nhân viên có mã nhân viên ở txtmanv private void butXoabo_Click(object sender, EventArgs e) { DialogResult dr = MessageBox.Show("Chắc chắn xóa hay không ?", "Thông báo", MessageBoxButtons.YesNo); if (dr == DialogResult.Yes) // Nếu người sử dụng chọn nút yes { Moketnoi(); string Sql = "delete from nhanvien where manhanvien='" + txtmanv.Text + "'"; cmdXoa = new SqlCommand(Sql,cn); if (cmdXoa.ExecuteNonQuery() == 1) { MessageBox.Show("Xóa thành công"); LoadListView(); LoadItem(0); } else MessageBox.Show("Không tồn tại mã nhân viên " + txtmanv.Text); cmdXoa.Dispose(); } } 3.5 Làm việc với mô hình Ngắt kết nối: DataSet và DataTable Mô hình Ngắt Kết nối của ADO.NET dựa trên cơ sở sử dụng đối tượng DataSet như là một vùng nhớ đệm. Một đối tượng DataAdapter làm nhiệm vụ trung gian giữa DataSet và data source (nguồn dữ liệu) để nạp dữ liệu vào vùng nhớ đệm. Sau khi DataAdapter hoàn thành nhiệm vụ nạp dữ liệu, nó sẽ trả đối tượng Connection về pool, vì thế nó ngắt kết nối khỏi nguồn dữ liệu. 3.4.1 Lớp DataSet DataSet đóng vai trò của một CSDL in-memory (CSDL nằm trong bộ nhớ). Thuộc tính Tables của DataSet là một tập hợp các DataTable chứa dữ liệu và lược đồ dữ liệu (data schema) mô tả dữ liệu trong DataTable. Thuộc tính Relations chứa tập hợp các đối tượng DataRelation xác định cách thức liên kết các đối tượng DataTable của DataSet. Lớp DataSet cũng hỗ trợ việc sao chép, trộn, và xóa DataSet thông qua các phương thức tương ứng là Copy, Merge, và Clear. DataSet và DataTable là phần lõi của ADO.NET và chúng không là đặc trưng của một data provider nào (giống như ở các lớp Connection, DataReader, DataAdapter). Một ứng dụng có thể định nghĩa và nạp dữ liệu từ nguồn bất kỳ (chứ không nhất thiết là từ một CSDL) vào DataSet. Bài tập thực hành Chuyên đề Visual Studio .NET 96
  • 97. Bên cạnh các DataTable và các DataRelation, một DataSet còn có thể chứa các thông tin tùy biến khác được định nghĩa bởi ứng dụng. Hình dưới đây mô tả cả lớp chính trong DataSet. Trong số các thuộc tính này, chú ý thuộc tính PropertyCollection; đó là các thuộc tính được lưu trữ dưới dạng một hash table (bảng băm), thường chứa một giá trị time stamp hay các thông tin đặc tả như các yêu cầu hợp lệ hóa (validation requirements) cho column trong các DataTable trong DataSet. 3.4.1.1 DataTable Thuộc tính DataSet.Tables chứa các đối tượng DataTable. Mỗi đối tượng trong tập hợp này có thể được truy xuất bằng chỉ số hoặc bằng tên. Các DataTable trong tập hợp DataSet.DataTables mô phỏng các Table trong CSDL quan hệ (các row, column, …). Các thuộc tính quan trọng nhất của lớp DataTable là Columns và Rows định nghĩa cấu trúc và nội dung bảng dữ liệu. 3.4.1.2 DataColumn Thuộc tính DataTable.Columns chứa một tập các đối tượng DataColumn biểu diễn các trường dữ liệu trong DataTable. Bảng dưới đây tóm tắt các thuộc tính quan trọng của lớp DataColumn. Phương thức Mô tả ColumnName Tên column Bài tập thực hành Chuyên đề Visual Studio .NET 97
  • 98. Phương thức Mô tả DataType Kiểu của dữ liệu chứa trong column này Ví dụ: col1.DataType = System.Type.GetType("System.String") MaxLength Độ dài tối đa của một text column. -1 nếu không xác định độ dài tối đa ReadOnly Cho biết giá trị của column có được chỉnh sửa hay không AllowDBNull Giá trị Boolean cho biết column này có được chứa giá trị NULL hay không Unique Giá trị Boolean cho biết column này có được chứa các giá trị trùng nhau hay không Expression Biểu thức định nghĩa cách tính giá trị của một column Ví dụ: colTax.Expression = "colSales * .085"; Caption Tiêu đề hiển thị trong thành phần điều khiển giao diện đồ họa DataTable Tên của đối tượng DataTable chứa column này Các column của DataTable được tạo ra một cách tự động khi table được nạp dữ liệu từ kết quả của một database query hoặc từ kết quả đọc được ở một file XML. Tuy nhiên, chúng ta cũng có thể viết code để tạo động các column. Đoạn code dưới đây sẽ tạo ra một đối tượng DataTable, sau đó tạo thêm các đối tượng DataColumn, gán giá trị cho các thuộc tính của column, và bổ sung các DataColumn này vào DataTable. DataTable tb = new DataTable("DonHang"); DataColumn dCol = new DataColumn("MaSo", Type.GetType("System.Int16")); dCol.Unique = true; // Dữ liệu của các dòng ở column này không được trùng nhau dCol.AllowDBNull = false; tb.Columns.Add(dCol); dCol = new DataColumn("DonGia", Type.GetType("System.Decimal")); tb.Columns.Add(dCol); dCol = new DataColumn("SoLuong",Type.GetType("System.Int16")); tb.Columns.Add(dCol); Bài tập thực hành Chuyên đề Visual Studio .NET 98
  • 99. dCol= new DataColumn("ThanhTien",Type.GetType("System.Decimal")); dCol.Expression= "SoLuong*DonGia"; tb.Columns.Add(dCol); // Liệt kê danh sách các Column trong DataTable foreach (DataColumn dc in tb.Columns) { Console.WriteLine(dc.ColumnName); Console.WriteLine(dc.DataType.ToString()); } Để ý rằng column MaSo được định nghĩa để chứa các giá trị duy nhất. Ràng buộc này giúp cho column này có thể được dùng như là trường khóa để thiết lập relationship kiểu parent-child với một bảng khác trong DataSet. Để mô tả, khóa phải là duy nhất – như trong trường hợp này – hoặc được định nghĩa như là một primary key của bảng. Ví dụ dưới đây mô tả cách xác định primary key của bảng: DataColumn[] col = {tb.Columns["MaSo"]}; tb.PrimaryKey = col; Nếu một primary key chứa nhiều hơn 1 column – chẳng hạn như HoDem và Ten – bạn có thể tạo ra một ràng buộc unique constraint trên các như ví dụ dưới đây: DataColumn[] cols = {tb.Columns["HoDem"], tb.Columns["Ten"]}; tb.Constraints.Add(new UniqueConstraint("keyHoVaTen", cols)); Chúng ta sẽ xem xét cách thức tạo relationship cho các bảng và trộn dữ liệu ở phần tiếp theo. 3.4.1.3 DataRows Dữ liệu được đưa vào table bằng cách tạo mới một đối tượng DataRow, gán giá trị cho các column của nó, sau đó bổ sung đối tượng DataRow này vào tập hợp Rows gồm các DataRow của table. DataRow row; row = tb.NewRow(); // Tạo mới DataRow row["DonGia"] = 22.95; row["SoLuong"] = 2; row["MaSo"] = 12001; tb.Rows.Add(row); // Bổ sung row vào tập Rows Console.WriteLine(tb.Rows[0]["ThanhTien"].ToString()); // 45.90 Bài tập thực hành Chuyên đề Visual Studio .NET 99
  • 100. Một DataTable có các phương thức cho phép nó có thể commit hay roll back các thay đổi được tạo ra đối với table tương ứng. Để thực hiện được điều này, nó phải nắm giữ trạng thái của mỗi dòng dữ liệu bằng thuộc tính DataRow.RowState. Thuộc tính này được thiết lập bằng một trong 5 giá trị kiểu enumeration DataRowState sau: Added, Deleted, Detached, Modifed, hoặc Unchanged. Xem xét ví dụ sau: tb.Rows.Add(row); // Added tb.AcceptChanges(); // ...Commit changes Console.Write(row.RowState); // Unchanged tb.Rows[0].Delete(); // Deleted // Undo deletion tb.RejectChanges(); // ...Roll back Console.Write(tb.Rows[0].RowState); // Unchanged DataRow myRow; MyRow = tb.NewRow(); // Detached Hai phương thức AcceptChanges và RejectChanges của DataTable là tương đương với các thao tác commit và rollback trong một CSDL. Các phương thức này sẽ cập nhất mọi thay đổi xảy ra kể từ khi table được nạp, hoặc từ khi phương thức AcceptChanges được triệu gọi trước đó. Ở ví dụ trên, chúng ta có thể khôi phục lại dòng bị xóa do thao tác xóa là chưa được commit trước khi phương thức RejectChanges được gọi. Điều đáng lưu ý nhất đó là, những thay đổi được thực hiện là ở trên table chứ không phải là ở data source. ADO.NET quản lý 2 giá trị - ứng với 2 phiên bản hiện tại và nguyên gốc - cho mỗi column trong một dòng dữ liệu. Khi phương thức RejectChanges được gọi, các giá trị hiện tại sẽ được đặt khôi phục lại từ giá trị nguyên gốc. Điều ngược lại được thực hiện khi gọi phương thức AcceptChanges. Hai tập giá trị này có thể được truy xuất đồng thời thông qua các giá trị liệt kê DataRowVersion là: Current và Original: DataRow r = tb.Rows[0]; r["DonGia"]= 14.95; r.AcceptChanges(); r["DonGia"]= 16.95; Bài tập thực hành Chuyên đề Visual Studio .NET 100
  • 101. Console.WriteLine("Current: {0} Original: {1} ", r["Price", DataRowVersion.Current], r["Price", DataRowVersion.Original]); Kết quả in ra: Current: 16.95 Original: 14.95 3.4.1.4 DataView. DataView đóng vai trò như tầng hiển thị dữ liệu lưu trữ trong DataTable. Nó cho phép người sử dụng sắp xếp, lọc và tìm kiếm dữ liệu. //Giả sử đã có 1 dataset có tên là ds chứa dữ liệu của bảng DonHang DataView dv = new DataView(ds.Tables["DonHang”]; // Lọc ra tất cả các hàng có giá từ 10 đến 100 dv.RowFilter = "soluong>=10 and soluong<=100"; //Sắp xếp tăng dần theo số lượng nếu số lượng bằng nhau thì sắp xếp giảm dần thêm đơn giá dv.Sort = "soluong, dongia DESC"; 3.4.2 Nạp dữ liệu vào DataSet Chúng ta đã biết cách thành lập một DataTable và xử lý dữ liệu theo kiểu từng dòng một. Phần này sẽ trình bày phương pháp để dữ liệu và lược đồ dữ liệu được nạp tự động từ CSDL quan hệ vào các table trong DataSet. 3.4.2.1 Dùng DataReader để nạp dữ liệu vào DataSet Đối tượng DataReader có thể được sử dụng để liên hợp đối tượng DataSet hay DataTable trong việc nạp các dòng dữ liệu kết quả (của query trong DataReader). cmd.CommandText = "SELECT * FROM nhanvien"; DBDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection); DataTable dt = new DataTable("nhanvien"); dt.Load(rdr); // Nạp dữ liệu và lược đồ vào table Console.WriteLine(rdr.IsClosed); // True Bài tập thực hành Chuyên đề Visual Studio .NET 101
  • 102. Đối tượng DataReader được tự động đóng sau khi tất cả các dòng dữ liệu được nạp vào table. Do đã sử dụng tham số CommandBehavior.CloseConnection trong phương thức ExecuteReader nên connection được đóng sau khi DataReader được đóng. Nếu table đã có dữ liệu, phương thức Load sẽ trộn dữ liệu mới với các dòng dữ liệu đang có trong nó. Việc trộn này xảy ra chỉ khi các dòng dữ liệu có chung primary key. Nếu không có primary key được định nghĩa, các dòng dữ liệu sẽ được nối vào sau tập dữ liệu hiện tại. Chúng ta có thể sử dụng phương thức nạp chồng khác của phương thức Load để quy định cách thức làm việc. Phương thức Load với tham số kiểu enumeration LoadOption gồm 1 trong 3 giá trị OverwriteRow, PreserveCurrentValues, hoặc UpdateCurrentValues tương ứng với tùy chọn ghi đè nguyên dòng, giữ lại các giá trị hiện tại, hoặc cập nhật các giá trị hiện tại. Đoạn code dưới đây minh họa cách trộn dữ liệu vào các dòng hiện tại theo kiểu ghi đè các giá trị hiện tại: cmd.CommandText = "SELECT * FROM nhanvien WHERE diachi=’a’"; DBDataReader rdr = cmd.ExecuteReader(); DataTable dt = new DataTable("nhanvien"); dt.Load(rdr); Console.Write(dt.Rows[0]["HoTen"]); // giả sử giá trị nhận được là “tnv spider” // Gán khóa chính DataColumn[] col = new DataColumn[1]; col[0] = dt.Columns["Manv"]; dt.PrimaryKey = col; DataRow r = dt.Rows[0]; // lấy dòng đầu tiên r["HoTen"] = "ten moi"; // thay đổi giá trị của cột HoTen // Do reader đã bị đóng sau khi nạp vào data table nên phải giờ phải fill lại rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection); // Trộn dữ liệu với các dòng hiện tại. Ghi đè các giá trị hiện tại dt.Load(rdr, LoadOption.UpdateCurrentValues); // Giá trị cập nhật đã bị ghi đè!!! Console.Write(dt.Rows[0]["HoTen"]); // “tnv spider” 3.4.2.2 Nạp dữ liệu vào DataSet bằng DataAdapter Đối tượng DataAdapter có thể được dùng để nạp một table hiện có vào một table khác, hoặc tạo mới và nạp dữ liệu cho table từ kết quả của một query. Bước đầu tiên là tạo ra Bài tập thực hành Chuyên đề Visual Studio .NET 102
  • 103. một đối tượng DataAdapter tương ứng với data provider cụ thể. Dưới đây là các ví dụ để tạo ra đối tượng DataAdapter: (1) Tạo từ Connection string và câu truy vấn SELECT: String sql = "SELECT * FROM nhanvien"; SqlDataAdapter da = new SqlDataAdapter(sql, connStr); (2) Tạo từ đối tượng Connection và câu truy vấn SELECT: SqlConnection conn = new SqlConnection(connStr); SqlDataAdapter da = new SqlDataAdapter(sql, conn); (3) Gán đối tượng Command cho thuộc tính SelectCommand SqlDataAdapter da = new SqlDataAdapter(); SqlConnection conn = new SqlConnection(connStr); da.SelectCommand = new SqlCommand(sql, conn); Sau khi đối tượng DataAdapter đã được tạo ra, phương thức Fill của nó được thực thi để nạp dữ liệu vào table (đang tồn tại hoặc tạo mới). Ở ví dụ dưới đây, một table mới được tạo ra với tên mặc định là “Table”: DataSet ds = new DataSet(); // Tạo ra một DataTable, nạp dữ liệu vào DataTable, và đưa DataTable vào DataSet int nRecs = da.Fill(ds); // trả về số lượng record được nạp vào DataTable // Nếu muốn đặt tên cho DataTable trong DataSet thay vì lấy tên mặc định // thì sử dụng code như thế này int nRecs = da.Fill(ds, "nhanvien ") Với một table đang tồn tại, tác dụng của lệnh Fill tùy thuộc vào table có primary hay không. Nếu có, những dòng dữ liệu có khóa trùng với dòng dữ liệu mới sẽ được thay thế. Các dòng dữ liệu mới không trùng với dữ liệu hiện có sẽ được nối vào sau DataTable. 3.4.3 Ví dụ Ví dụ về DataAdapter và DataSet Ví dụ này sử dụng cơ sở dữ liệu quanlythuvien như trong ví dụ 3.3.4 Thiết kế form frmtimkiemsach để tìm theo tên sách hoặc tên tác giả như sau: Bài tập thực hành Chuyên đề Visual Studio .NET 103
  • 104. frmtimkiemsach sử dụng các trường, phương thức và sự kiện sau: Các điều khiển Tên điều khiển Thuộc tính Form Name: frmtimkiemsach Text:Tìm kiếm theo nhan đề hoặc tên tác giả Label Text: Nhập tên sách hoặc tên tác giả cần tìm TextBox Name: txttimkiem dataGridView Name: dataGridView1 statusStrip Name: thanhtrangthai Items: Add thêm 2 statusLabel: với tên ketquatim và tóngoluong Các trường: Tên trường Ý nghĩa Cn Dùng để kết nối đến cơ sở dữ liệu quanlythuvien cmd sqlCommand sử dụng câu lệnh select để hiển thị và tìm kiếm sách da SqlDataAdapter chứa cmd và cn Bài tập thực hành Chuyên đề Visual Studio .NET 104
  • 105. ds DataSet chứa dữ liệu của bảng sách hoặc chứa kết quả tìm kiếm Các phương thức + Hàm dựng frmtimkiemsach để tạo giao diện public frmtimkiemsach() { InitializeComponent(); } + Phương thức Tongsoluong: được sử dụng để tính tổng số lượng sách của các sách lưu trong Dataset ds. private int Tongsoluong() { int s=0; foreach (DataRow r in ds.Tables["sach"].Rows) { s += (int)r["soluong"]; } return s; } + Phương thức Tongsoluongtk tính tổng số lượng sách trong DataView dv, dv chứa thông tin các sách tìm kiếm được. private int Tongsoluongtk(DataView dv) { int s = 0; foreach (DataRow r in dv.ToTable("sach").Rows ) { s += (int)r["soluong"]; } return s; } + Sự kiện frmtimkiemsach_Load:Nạp thông tin của 4 quyển sách đầu tiên theo thứ tự giảm dần của ngaynhap vào DataSet ds với tên bảng trong DataSet là sach, sau đó hiển thị thông tin của bảng sach trong DataSet vào dataGridView1, đưa tổng số sách và tổng số lượng sách trong DataSet vào thanh trạng thái private void frmtimkiemsach_Load(object sender, EventArgs e) { cn.Open(); // Mở kết nối cmd.CommandText = "select top 4 * from sach order by ngaynhap desc" ; cmd.Connection = cn; da.SelectCommand = cmd; da.Fill(ds, "sach"); // Nạp dữ liệu vào DataSet dataGridView1.DataSource = ds.Tables["sach"]; // Nạp dữ liệu vào dataGridView1 // Nạp dữ liệu vào thanh trạng thái Bài tập thực hành Chuyên đề Visual Studio .NET 105
  • 106. thanhtrangthai.Items[0].Text = "Tổng số sách:" + ds.Tables["sach"].Rows.Count.ToString(); thanhtrangthai.Items[1].Text = "Tổng số lượng:" + Tongsoluong().ToString(); } + Sự kiện txttimkiem_KeyPress: Khi người sử dụng nhấn Enter trên txttimkiem thì việc tìm kiếm tương đối bắt đầu: Tạo ra 1 DataView dv chứa dữ liệu của bảng sách trong DataSet ds, lọc trong DataView dv ra thông tin của các quyển sách gần giống với dữ liệu nhập trên txttimkiem, sau đó đưa kết quả lọc ra trên dataGridView1 và thanh trạng thái. private void txttimkiem_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 13) { DataView dv = new DataView(ds.Tables["sach"]); //Nạp dữ liệu vào DataView //bắt đầu lọc dữ liệu dv.RowFilter = "nhande like '%" + txttimkiem.Text + "%' or tacgia like '%" + txttimkiem.Text + "%'"; dataGridView1.DataSource = dv; //Nạp kết quả lọc trong dv vào dataGridView1 // Đưa số quyển sách và tổng số lượng sách lọc được vào thanh trang thái thanhtrangthai.Items[0].Text = "Số kết quả tìm thấy được: " + dv.Count.ToString() + "/" + ds.Tables["sach"].Rows.Count.ToString(); thanhtrangthai.Items[1].Text = "Tổng số lượng tìm thấy được:" + Tongsoluongtk(dv).ToString() + "/" + Tongsoluong().ToString(); } } 3.4.4 Cập nhật CSDL bằng DataAdapter Sau khi DataAdapter đã nạp dữ liệu vào table, connection sẽ được đóng, và các thay đổi sau đó đối sau đó tạo ra cho dữ liệu sẽ chỉ có ảnh hưởng trong DataSet chứ không phải là ở dữ liệu nguồn! Để thực sự cập nhật các thay đổi này lên nguồn dữ liệu, DataAdapter phải được sử dụng để khôi phục connection và gửi các dòng dữ liệu đã được thay đổi lên CSDL. Ngoài SelectCommand, DataAdapter có thêm 3 thuộc tính Command nữa, gồm InsertCommand, DeleteCommand và UpdateCommand, làm nhiệm vụ thực hiện các thao tác tương ứng với tên thuộc tính của chúng (chèn, xóa, cập nhật). Các Command này được thực thi khi phương thức Update của DataAdapter được triệu gọi. Khó khăn nằm ở chỗ tạo ra các query command phức tạp này (cú pháp của câu lệnh SQL tương ứng càng dài dòng và phức tạp khi số lượng column nhiều lên). Rất may là các data provider đều có cài đặt một lớp gọi là CommandBuilder dùng để quản lý việc tạo các Command nói trên một cách tự động. Bài tập thực hành Chuyên đề Visual Studio .NET 106
  • 107. 3.4.4.1 CommandBuilder Một đối tượng CommandBuilder sẽ sinh ra các Command cần thiết để thực hiện việc cập nhật nguồn dữ liệu tạo ra bởi DataSet. Cách tạo đối tượng CommandBuilder là truyền đối tượng DataAdapter cho phương thức khởi dựng của nó; sau đó, khi phương thức DataAdapter.Update được gọi, các lệnh SQL sẽ được sinh ra và thực thi. Đoạn code dưới đây minh họa cách thức thay đổi dữ liệu ở một DataTable và cập nhật lên CSDL tương ứng bằng DataAdapter: //Giả sử đã có 1 DataSet ds chứa dữ liệu của bảng khoa DataTable dt= ds.Tables["khoa"]; // (1) Dùng commandBuilder để sinh ra các Command cần thiết để update SqlCommandBuilder sb = new SqlCommandBuilder(da); // (2) Thực hiện thay đổi dữ liệu: thêm 1 khoa mới DataRow drow = dt.NewRow(); drow["Makhoa"] = 12; drow["tenkhoa"] = "abc"; dt.Rows.Add(drow); // (3) Thực hiện thay đổi dữ liệu: xóa 1 khoa dt.Rows[4].Delete(); // (4) Thực hiện thay đổi dữ liệu: thay đổi giá trị 1 dòng dữ liệu dt.Rows[5]["tenkhoa"] = "this must be changed"; // (5) Tiến hành cập nhật lên CSDL int nUpdate = da.Update(ds, "khoa"); MessageBox.Show("Số dòng được thay đổi: " + nUpdate.ToString()); //  3 Có một số hạn chế khi sử dụng CommandBuilder: Command Select ứng với DataAdapter chỉ được tham chiếu đến 1 table, và table nguồn trong CSDL phải bao gồm một primary key hoặc một column chứa các giá trị duy nhất. Column này (hay tổ hợp các columns) phải được bao gồm trong command Select ban đầu. 3.4.4.2 Đồng bộ hóa dữ liệu giữa DataSet và CSDL Như đã minh họa trong ví dụ này, việc sử dụng DataAdapter làm đơn giản hóa và tự động hóa quá trình cập nhật CSDL hoặc bất kỳ data source nào. Tuy nhiên, có một vấn đề ở đây: multi-user (nhiều người sử dụng). Mô hình Ngắt kết nối được dựa trên cơ chế Optimistic Concurrency, một cách tiếp cận mà trong đó các dòng dữ liệu ở data source không bị khóa (lock) giữa thời gian mà chúng được đọc và thời gian mà các cập nhật được áp dụng cho data source. Trong khoảng thời gian này, user khác có thể cũng Bài tập thực hành Chuyên đề Visual Studio .NET 107
  • 108. cập nhật data source. Nếu có thay đổi xảy ra kể từ lần đọc trước đó thì phương thức Update sẽ nhận biết được và không cho áp dụng thay đổi đối với các dòng dữ liệu đó. Có hai phương án cơ bản để giải quyết lỗi concurrency (tương tranh) khi có nhiều cập nhật được áp dụng: roll back tất cả các thay đổi nếu như xuất hiện xung đột (violation), hoặc áp dụng các cập nhật không gây ra lỗi và xác định những cập nhật có gây ra lỗi để có thể xử lý lại. 3.4.4.3 Sử dụng Transactions để Roll Back nhiều cập nhật Khi thuộc tính DataAdapter.ContinueUpdateOnErrors được thiết lập là false, một ngoại lệ sẽ được ném ra khi một thay đổi dòng dữ liệu không thể thực hiện được. Điều này sẽ ngăn các cập nhật tiếp theo được thực thi, nhưng lại không ảnh hưởng đến các cập nhật đã xuất hiện trước ngoại lệ đó. Do những cập nhật có thể có liên quan với nhau, ứng dụng thường là cần chiến lược hoặc là tất cả, hoặc là không (all-or-none). Cách dễ nhất để thực thi chiến lược này là tạo ra một transaction trong đó tất cả các command update sẽ được thực thi. Để thực hiện điều này, tạo ra một đối tượng SqlTransaction và gắn nó với SqlDataAdapter.SelectCommand bằng cách truyền nó cho hàm khởi dựng của nó. Nếu có ngoại lệ xảy ra, phương thức Rollback sẽ được thực thi để undo mọi thay đổi trước đó; nếu không có ngoại lệ nào xuất hiện, phương thức Commit được thực thi để áp dụng tất cả các command update. Dưới đây là một ví dụ: SqlDataAdapter da = new SqlDataAdapter(); SqlCommandBuilder sb = new SqlCommandBuilder(da); SqlTransaction tran; SqlConnection conn = new SqlConnection(connStr); conn.Open(); // Connection phải được dùng với Transaction // (1) Tạo ra một transaction SqlTransaction tran = conn.BeginTransaction(); // (2) Gắn SelectCommand với transaction da.SelectCommand = new SqlCommand(sql, conn, tran); DataSet ds = new DataSet(); da.Fill(ds, "docgia"); // // Code ở phần này thực hiện các cập nhật lên các dòng dữ liệu ở DataSet try Bài tập thực hành Chuyên đề Visual Studio .NET 108
  • 109. { int updates = da.Update(ds, "docgia"); MessageBox.Show("Cập nhật: " + updates.ToString()); } // (3) Nếu có ngoại lệ xuất hiện, roll back mọi cập nhật trong transaction catch (Exception ex) { MessageBox.Show(ex.Message); // Lỗi khi cập nhật if (tran != null) { tran.Rollback(); // Roll back mọi cập nhật tran = null; MessageBox.Show("Tất cả các cập nhật đã bị roll back."); } } finally { // (4) Nếu không có lỗi, commit tất cả các cập nhật if (tran != null) { tran.Commit(); MessageBox.Show("Tất cả đã được cập nhật thành công. "); tran = null; } } conn.Close(); 3.4.4.4 Xác định các dòng gây ra lỗi cập nhật Khi thuộc tính DataAdapter.ContinueUpdateOnErrors được thiết lập là True, các xử lý sẽ không ngưng nếu có một dòng dữ liệu không thể cập nhật được. Thay vào đó, DataAdapter sẽ cập nhật tất cả các dòng dữ liệu không gây ra lỗi. Sau đó, lập trình viên có thể xác định các dòng dữ liệu không cập nhật được và quyết định cách xử lý chúng. Các dòng dữ liệu không cập nhật được có thể dễ dàng được xác định qua thuộc tính DataRowState của chúng (đã được trình bày trong phần mô tả DataRow). Các dòng dữ liệu đã được cập nhật thành công sẽ có trạng thái Unchanged; trong khi đó các dòng dữ liệu không cập nhật thành công sẽ mang trạng thái Added, Deleted hoặc Bài tập thực hành Chuyên đề Visual Studio .NET 109
  • 110. Modified. Đoạn code dưới đây minh họa cách lặp qua các dòng dữ liệu và xác định các dòng chưa được cập nhật. // SqlDataAdapter da nạp dữ liệu của bảng docgia da.ContinueUpdateOnError = true; DataSet ds = new DataSet(); try { da.Fill(ds, "docgia"); DataTable dt = ds.Tables["docgia"]; SqlCommandBuilder sb = new SqlCommandBuilder(da); // ... Các thao tác cập nhật dt.Rows[29].Delete(); // Delete dt.Rows[30]["HoTen"] = "try to change"; // Update dt.Rows[30][Madocgia] = 1234; // Update dt.Rows[31]["HoTen"] = "XYZ"; // Update DataRow drow = dt.NewRow(); drow["HoTen"] = "tnv spider"; drow["Madocgia"] = 25; dt.Rows.Add(drow); // insert // Submit updates int updates = da.Update(ds, "docgia"); if (ds.HasChanges()) { // Load rows that failed into a DataSet DataSet failures = ds.GetChanges(); int rowsFailed = failures.Rows.Count; Console.WriteLine("Số dòng không thể cập nhật: " + rowsFailed); foreach (DataRow r in failures.Tables[0].Rows ) { string state = r.RowState.ToString()); Bài tập thực hành Chuyên đề Visual Studio .NET 110
  • 111. // Phải hủy bỏ thay đổi để hiển thị dòng đã bị xóa if (r.RowState == DataRowState.Deleted) r.RejectChanges(); string iMadocgia= ((int)r["Madocgia"]).ToString(); string msg = state + " Madocgia: " + iMadocgia; Console.WriteLine(msg); } } Lưu ý rằng ngay cả khi thao tác xóa xuất hiện trước, nó cũng không có tác dụng đối với các thao tác khác. Câu lệnh SQL xóa hay cập nhật một dòng dữ liệu được dựa theo giá trị của primary key chứ không liên quan đến vị trí của nó. Ngoài ra các cập nhật trên cùng một dòng được kết hợp và đếm như là 1 cập nhật cho dòng đó bởi phương thức Update. Ở ví dụ trên, các cập nhật cho dòng 30 được tính như là 1 cập nhật. 3.4.5 Định nghĩa Relationships giữa các Table trong DataSet Một DataRelation là một mối quan hệ parent-child giữa hai đối tượng DataTables. Nó được định nghĩa dựa trên việc so khớp các columns trong 2 DataTable. Cú pháp hàm khởi dựng là như sau: public DataRelation( string relationName, DataColumn parentColumn, DataColumn childColumn) Một DataSet có một thuộc tính Relations giúp quản lý tập hợp các DataRelation đã được định nghĩa trong DataSet. Sử dụng phương thức Relations.Add để thêm các DataRelation vào tập hợp Relations. Ví dụ dưới đây thiết lập mối quan hệ giữa hai bảng khoa và docgia để có thể liệt kê danh sách các docgia của mỗi khoa. string connStr="Data Source=tên máy chủ;Initial Catalog=quanlythuvien; Trusted_Connection=yes"; DataSet ds = new DataSet(); // (1) Fill bảng docgia string sql = "SELECT * FROM docgia”; SqlConnection conn = new SqlConnection(connStr); SqlCommand cmd = new SqlCommand(); SqlDataAdapter da = new SqlDataAdapter(sql, conn); da.Fill(ds, "docgia"); // (2) Fill bảng khoa sql = "SELECT * FROM khoa”; Bài tập thực hành Chuyên đề Visual Studio .NET 111
  • 112. da.SelectCommand.CommandText = sql; da.Fill(ds, "khoa"); // (3) Định nghĩa relationship giữa 2 bảng khoa và docgia DataTable parent = ds.Tables["khoa"]; DataTable child = ds.Tables["docgia"]; DataRelation relation = new DataRelation("khoa_docgia", parent.Columns["makhoa"], child.Columns["makhoa"]); // (4) Đưa relationship vào DataSet ds.Relations.Add(relation); // (5) Liệt kê danh sách các đọc giả của từng khoa foreach (DataRow r in parent.Rows) { Console.WriteLine(r["tenkhoa"]); // Tên khoa foreach (DataRow rc in r.GetChildRows("khoa_docgia")) { Console.WriteLine(" " + rc["HoTen"]); } } /* Ví dụ kết quả: Khoa Tin Nguyễn Văn Trung Ngô Anh Tuấn Lê Thanh Hoa Khoa Toán Nguyễn Thị Hoa Trần Văn Phúc */ Khi một relationship được định nghĩa giữa 2 tables, nó cũng sẽ thêm một ForeignKeyConstraint vào tập hợp Constraints của DataTable con. Constraint này quyết định cách mà DataTable con bị ảnh hưởng khi các dòng dữ liệu ở phía DataTable cha bị thay đổi hay bị xóa. Trong thực tế, điều này có nghĩa là khi bạn xóa 1 dòng trong DataTable cha, các dòng con có liên quan cũng bị xóa – hoặc cũng có thể là các key bị đặt lại giá trị thành NULL. Tương tự như thế, nếu một giá trị key bị thay đổi trong DataTable cha, các dòng dữ liệu có liên quan trong DataTable con cũng có thể bị thay đổi theo hoặc bị đổi giá trị thành NULL. Các luật như trên được xác định bởi các thuộc tính DeleteRule và UpdateRule của constraint. Các luật này được nhận các giá trị liệt kê sau đây: Bài tập thực hành Chuyên đề Visual Studio .NET 112
  • 113. - Cascade. Xóa/Cập nhật các dòng dữ liệu có liên quan trong DataTable con. Đây là giá trị mặc định. - None. Không làm gì. - SetDefault. Thiết lập các giá trị khóa trong các dòng dữ liệu có liên quan của DataTable con thành giá trị mặc định của column tương ứng. - SetNull. Thiết lập các giá trị khóa trong các dòng dữ liệu có liên quan của DataTable con thành null. Xem xét ví dụ dưới đây: // (1) Thêm một dòng với khóa mới vào DataTable con DataRow row = child.NewRow(); row["Makhoa"] = 999; // giả sử trong bảng khoa không có record nào có Makhoa = 999 child.Rows.Add(row); // Không được do 999 không tồn tại trong DataTable cha // (2) Xóa một dòng trong DataTable cha row = parent.Rows[0]; row.Delete(); // Xóa các dòng trong DataTable con có khóa này // (3) Tạm thời vô hiệu hóa constraints và thử thêm dòng mới ds.EnforceConstraints = false; row["Makhoa"] = 999; child.Rows.Add(row); // Được chấp nhận!!! ds.EnforceConstraints = true; // Kích hoạt constraint trở lại // (4) Thay đổi constraint để đặt các dòng dữ liệu thành null nếu DataTable thay đổi ((ForeignKeyConstraint)child.Constraints[0]).DeleteRule = Rule.SetNull; Lưu ý rằng thuộc tính EnforeceConstraint được đặt thành false sẽ làm vô hiệu hóa tất cả các constraint – điều này trong thuật ngữ CSDL gọi là bỏ qua tính toàn vẹn tham chiếu. Điều này cho phép một khoa được bổ sung vào thậm chí khi cả column Makhoa không tương ứng với dòng nào trong bảng khoa. Nó cũng cho phép một dòng khoa được xóa ngay cả khi có nhiều dòng tương ứng với nó trong bảng docgia. 3.5 Sử dụng Data Binding 3.6 Lựa chọn giữa mô hình Kết nối và mô hình Ngắt kết nối DataReader và DataSet đưa ra hai cách tiếp cận khác nhau để xử lý dữ liệu. DataReader cho phép truy xuất kiểu forward-only, read-only. Bằng cách xử lý từng Bài tập thực hành Chuyên đề Visual Studio .NET 113
  • 114. dòng một, cách tiếp cận này giúp tiết kiệm bộ nhớ. DataSet, ngược lại, cho phép truy xuất theo kiểu read/write, nhưng lại yêu cầu phải có đủ bộ nhớ để quản lý bản sao dữ liệu nạp về từ data source. Như vậy, bạn có thể suy ra một số quy tắc chung: Nếu ứng dụng không cần tính năng cập nhật dữ liệu và hầu như chỉ hiển thị và chọn lọc dữ liệu, DataReader là lựa chọn thích hợp; nếu ứng dụng cần cập nhật dữ liệu, giải pháp sử dụng DataSet nên được xem xét. Tất nhiên, cũng có một số tình huống đi ngược lại với các quy tắc chung nói trên. Chẳng hạn, nếu data source chứa một số lượng lớn các record, khi đó DataSet phải yêu cầu quá nhiều tài nguyên bộ nhớ; hoặc nếu dữ liệu chỉ yêu cầu một vài thao tác cập nhật, thì sự kết hợp giữa DataReader và Command để thực thi cập nhật sẽ có thể có ý nghĩa hơn. Tóm lại, một DataSet là một lựa chọn tốt khi: - Dữ liệu cần được serialize (tuần tự hóa) và/hoặc gửi đi bằng HTTP. - Các điều khiển read-only trên Form Win Form được kết buộc (bind) với data source. - Một điều khiển Win Form như GridView hay DataView được kết buộc với một data source có khả năng cập nhật được. - Một ứng dụng desktop cần thêm, xóa, sửa các dòng dữ liệu. Trong khi đó, DataReader là lựa chọn cho những trường hợp: - Cần quản lý một số lượng lớn các record, lớn đến mức mà bộ nhớ và thời gian để nạp dữ liệu cho DataSet là phi thực tế. - Dữ liệu là read-only và được kết buộc với một điều khiển loại danh sách (list control) của Win Form hoặc Web Form. - CSDL là không ổn định và thay đổi thường xuyên. Bài tập thực hành Chuyên đề Visual Studio .NET 114
  • 115. 3.6 Tạo đối tượng DataSet Trong ví dụ trước, tạo ra đối tượng SqlDataAdapter bằng cách gắn trực tiếp chuỗi kết nối và chuỗi truy vấn vào nó. Đối tượng Connection và Command sẽ được tạo và tích hợp vào trong đối tượng DataAdapter này. Với cách này, ta sẽ bị hạn chế trong các thao tác liên quan đến cơ sở dữ liệu. SqlDataAdapter DataAdapter = new SqlDataAdapter(commandString, connectionString); Ví dụ sau đây sẽ minh họa việc lấy về đối tượng DataSet bằng cách tạo ra các đối tượng Connection và Command một cách riêng biệt, khi ta cần dùng lại chúng hay muốn thực hiện hoàn chỉnh một thao tác thì sẽ thuận lợi hơn. Đầu tiên ta sẽ khai báo bốn biến thành viên thuộc lớp, như sau: private System.Data.SqlClient.SqlConnection myConnection; private System.Data.DataSet myDataSet; private System.Data.SqlClient.SqlCommand myCommand; private System.Data.SqlClient.SqlDataAdapter DataAdapter; Đối tượng Connection sẽ được tạo riêng với chuỗi kết nối: string connectionString = "server=localhost; uid=sa; pwd=; database=northwind"; myConnection = new System.Data.Sql.SqlConnection(connectionString); Sau đó ta sẽ mở kết nối: myConnection.Open( ); Ta có thể thực hiện nhiều giao tác trên cơ sở dữ liệu khi kết nối được mở và sau khi dùng xong ta chỉ đơn giản đóng kết nối lại. Tiếp theo ta sẽ tạo ra đối tượng DataSet: myDataSet = new System.Data.DataSet( ); Và tiếp tục tạo đối tượng Command, gắn cho nó đối tượng Connection đã mở và chuỗi truy vấn dữ liệu: myCommand = new System.Data.SqlClient.SqlCommand( ) myCommand.Connection=myConnection; myCommand.CommandText = "Select * from Customers"; Cuối cùng ta cần tạo ra đối tượng SqlDataAdapter, gắn đối tượng SqlCommand vừa tạo ở trên cho nó, đồng thời phải tiến hành ánh xạ bảng dữ liệu nó nhận được từ câu truy vấn của đối tượng Command để tạo sự đồng nhất về tên các cột khi đẩy bảng dữ liệu này vào DataSet. DataAdapter = new System.Data.SqlClient.SqlDataAdapter( ); Bài tập thực hành Chuyên đề Visual Studio .NET 115
  • 116. DataAdapter.SelectCommand= myCommand; DataAdapter.TableMappings.Add("Table","Customers"); DataAdapter.Fill(myDataSet); Bây giờ ta chỉ việc gắn DataSet vào thuộc tính DataSoucre của điều khiển lưới: dataGrid1.DataSource=myDataSet.Tables["Customers"].DefaultView; Dưới đây là mã hoàn chỉnh của ứng dụng này: using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.SqlClient; namespace ProgrammingCSharpWindows.Form { public class ADOForm1: System.Windows.Forms.Form { private System.ComponentModel.Container components; private System.Windows.Forms.DataGrid dataGrid1; private System.Data.SqlClient.SqlConnection myConnection; private System.Data.DataSet myDataSet; private System.Data.SqlClient.SqlCommand myCommand; private System.Data.SqlClient.SqlDataAdapter DataAdapter; public ADOForm1( ) { InitializeComponent( ); // tạo đối tượng connection và mở nó string connectionString = "server=Neptune; uid=sa; pwd=oWenmEany;" + "database=northwind"; Bài tập thực hành Chuyên đề Visual Studio .NET 116
  • 117. myConnection = new SqlConnection(connectionString); myConnection.Open(); // tạo đối tượng DataSet mới myDataSet = new DataSet( ); // tạo đối tượng command mới và gắn cho đối tượng // connectio và chuỗi truy vấn cho nó myCommand = new System.Data.SqlClient.SqlCommand( ); myCommand.Connection=myConnection; myCommand.CommandText = "Select * from Customers"; // tạo đối tượng DataAdapter với đối tượng Command vừa // tạo ở trên, đồng thời thực hiện ánh xạ bảng dữ liệu DataAdapter = new SqlDataAdapter( ); DataAdapter.SelectCommand= myCommand; DataAdapter.TableMappings.Add("Table","Customers"); // đẩy dữ liệu vào DataSet DataAdapter.Fill(myDataSet); // gắn dữ liệu vào lưới dataGrid1.DataSource = myDataSet.Tables["Customers"].DefaultView; } public override void Dispose() { base.Dispose(); components.Dispose(); } private void InitializeComponent( ) { this.components = new System.ComponentModel.Container(); this.dataGrid1 = new System.Windows.Forms.DataGrid(); dataGrid1.BeginInit(); Bài tập thực hành Chuyên đề Visual Studio .NET 117
  • 118. dataGrid1.Location = new System.Drawing.Point(24, 32); dataGrid1.Size = new System.Drawing.Size(480, 408); dataGrid1.DataMember = ""; dataGrid1.TabIndex = 0; this.Text = "ADOFrm1"; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(536, 501); this.Controls.Add(this.dataGrid1); dataGrid1.EndInit( ); } public static void Main(string[] args) { Application.Run(new ADOForm1()); } } } Giao diện của ví dụ này cũng tương tự như các ví dụ trên. 3.7 Kết hợp giữa nhiều bảng Các ví dụ ở trên chỉ đơn thuần lấy dữ liệu từ trong một bảng. Ở ví dụ này ta sẽ tìm hiểu về cách lấy dữ liệu trên hai bảng. Trong cơ sở dữ liệu của ta, một khách hàng có thể có nhiều hóa đơn khác nhau, vì thế ta sẽ có quan hệ một nhiều giữa bảng khách hàng (Customers)và bảng hóa đơn (Orders). Bảng Orders sẽ chứa thuộc tính CustomersId của bảng Customers, thuộc tính này đóng vai trò là khóa chính đối bảng Customers và khóa ngoại đối với bảng Orders. Ứng dụng của ta sẽ hiển thị dữ liệu của hai bảng Customers và Orders trên cùng một lưới và thể hiện quan hệ một nhiều của hai bảng ngay trên lưới. Để làm được điều này ta chỉ cần dùng chung một đối tượng Connetion, hai đối tượng tượng SqlDataAdapter và hai đối tượng SqlCommand. Sau khi tạo đối tượng SqlDataAdapter cho bảng Customers tương tự như ví dụ trên, ta tiến tạo tiếp đối tượng SqlDataAdapter cho bảng Orders: myCommand2 = new System.Data.SqlClient.SqlCommand(); DataAdapter2 = new System.Data.SqlClient.SqlDataAdapter(); myCommand2.Connection = myConnection; Bài tập thực hành Chuyên đề Visual Studio .NET 118
  • 119. myCommand2.CommandText = "SELECT * FROM Orders"; Lưu ý là ở đây đối tượng DataAdapter2 có thể dùng chung đối tượng Connection ở trên, nhưng đối tượng Command thì khác. Sau đó gắn đối tượng Command2 cho DataAdapter2, ánh xạ bảng dữ liệu và đẩy dữ liệu vào DataSet ở trên. DataAdapter2.SelectCommand = myCommand2; DataAdapter2.TableMappings.Add ("Table", "Orders"); DataAdapter2.Fill(myDataSet); Tại thời điểm này, ta có một đối tượng DataSet nhưng chứa hai bảng dữ liệu: Customers và Orders. Do ta cần thể hiện cả quan hệ của hai bảng ngay trên điều khiển lưới, cho nên ta cần phải định nghĩa quan hệ này cho đối tượng DataSet của chúng ta. Nếu không làm điều này thì đối tượng DataSet sẽ bỏ qua quan hệ giữa 2 bảng này. Do đó ta cần khai báo thêm đối tương DataRelation: System.Data.DataRelation dataRelation; Do mỗi bảng Customers và Orders đều có chứa một thuộc tính CustomersId, nên ta cũng cần khái báo thêm hai đối tượng DataColumn tương ứng với hai thuộc tính này. System.Data.DataColumn dataColumn1; System.Data.DataColumn dataColumn2; Mỗi một DataColumn sẽ giữ giá trị của một cột trong bảng của đối tượng DataSet: dataColumn1 = myDataSet.Tables["Customers"].Columns["CustomerID"]; dataColumn2 = myDataSet.Tables["Orders"].Columns["CustomerID"]; Ta tiến hành tạo quan hệ cho hai bảng bằng cách gọi hàm khởi tạo của đối tượng DataRelation, truyền vào cho nó tên quan hệ và hai cột cần tạo quan hệ: dataRelation = new System.Data.DataRelation("CustomersToOrders", dataColumn1, dataColumn2); Sau khi tạo được đối tượng DataRelation, ta thêm vào DataSet của ta. Sau đó ta cần tạo một đối tượng quản lý khung nhìn DataViewManager cho DataSet, đối tượng khung nhìn này sẽ được gán cho lưới điều khiển để hiển thị: myDataSet.Relations.Add(dataRelation); DataViewManager DataSetView = myDataSet.DefaultViewManager; dataGrid1.DataSource = DataSetView; Do điều khiển lưới phải hiển thị quan hệ của hai bảng dữ liệu, nên ta phải chỉ cho nó biết là bảng nào sẽ là bảng cha. Ở đây bảng cha là bảng Customers: dataGrid1.DataMember= "Customers"; Bài tập thực hành Chuyên đề Visual Studio .NET 119
  • 120. Sau đây là mã hoàn chỉnh của toàn bộ ứng dụng: using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.SqlClient; namespace ProgrammingCSharpWindows.Form { public class ADOForm1: System.Windows.Forms.Form { private System.ComponentModel.Container components; private System.Windows.Forms.DataGrid dataGrid1; private System.Data.SqlClient.SqlConnection myConnection; private System.Data.DataSet myDataSet; private System.Data.SqlClient.SqlCommand myCommand; private System.Data.SqlClient.SqlCommand myCommand2; private System.Data.SqlClient.SqlDataAdapter DataAdapter; private System.Data.SqlClient.SqlDataAdapter DataAdapter2; public ADOForm1( ) { InitializeComponent( ); // tạo kết nối string connectionString = "server=Neptune; uid=sa;" + " pwd=oWenmEany; database=northwind"; myConnection = new SqlConnection(connectionString); myConnection.Open( ); // tạo DataSet myDataSet = new System.Data.DataSet( ); Bài tập thực hành Chuyên đề Visual Studio .NET 120
  • 121. // tạo đối tượng Command và DataSet cho bảng Customers myCommand = new System.Data.SqlClient.SqlCommand( ); myCommand.Connection=myConnection; myCommand.CommandText = "Select * from Customers"; DataAdapter =new System.Data.SqlClient.SqlDataAdapter(); DataAdapter.SelectCommand= myCommand; DataAdapter.TableMappings.Add("Table","Customers"); DataAdapter.Fill(myDataSet); // tạo đối tượng Command và DataSet cho bảng Orders myCommand2 = new System.Data.SqlClient.SqlCommand( ); DataAdapter2=new System.Data.SqlClient.SqlDataAdapter(); myCommand2.Connection = myConnection; myCommand2.CommandText = "SELECT * FROM Orders"; DataAdapter2.SelectCommand = myCommand2; DataAdapter2.TableMappings.Add ("Table", "Orders"); DataAdapter2.Fill(myDataSet); // thiết lập quan hệ giữa 2 bảng System.Data.DataRelation dataRelation; System.Data.DataColumn dataColumn1; System.Data.DataColumn dataColumn2; dataColumn1 = myDataSet.Tables["Customers"].Columns["CustomerID"]; dataColumn2 = myDataSet.Tables["Orders"].Columns["CustomerID"]; dataRelation = new System.Data.DataRelation( "CustomersToOrders", dataColumn1, dataColumn2); // thêm quan hệ trên vào DataSet myDataSet.Relations.Add(dataRelation); // Đặt khung nhìn và bảng hiển thị trước cho lưới DataViewManager DataSetView = Bài tập thực hành Chuyên đề Visual Studio .NET 121
  • 122. myDataSet.DefaultViewManager; dataGrid1.DataSource = DataSetView; dataGrid1.DataMember= "Customers"; } public override void Dispose( ) { base.Dispose( ); components.Dispose( ); } private void InitializeComponent( ) { this.components = new System.ComponentModel.Container(); this.dataGrid1 = new System.Windows.Forms.DataGrid(); dataGrid1.BeginInit( ); dataGrid1.Location = new System.Drawing.Point(24, 32); dataGrid1.Size = new System.Drawing.Size(480, 408); dataGrid1.DataMember = ""; dataGrid1.TabIndex = 0; this.Text = "ADOFrm1"; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size (536, 501); this.Controls.Add (this.dataGrid1); dataGrid1.EndInit ( ); } public static void Main(string[] args) { Application.Run(new ADOForm1( )); } } } Bài tập thực hành Chuyên đề Visual Studio .NET 122
  • 123. 3.8 Thay đổi các bản ghi của cơ sở dữ liệu Tới lúc này, chúng ta đã học cách lấy dữ liệu từ cơ sở dữ liệu sau đó hiển thị chúng ra màn hình dựa vào các điều khiển có hay không kết buộc dữ liệu. Phần này chúng ta sẽ tìm hiểu cách cập nhật vào cơ sở dữ liệu. Các thao tác trên cơ sở dữ liệu như: Thêm, xóa và sửa một dòng trong các bảng dữ liệu. Sau đây là luồng công việc hoàn chỉnh khi ta có một thao tác cập nhật cơ sở dữ liệu: 1. Đẩy dữ liệu của bảng vào DataSet bằng câu truy vấn SQL hay gọi thủ tục từ cơ sở dữ liệu 2. Hiển thị dữ liệu trong các bảng có trong DataSet bằng cách kết buộc hay duyệt qua các dòng dữ liệu. 3. Hiệu chỉnh dữ liệu trong các bảng DataTable với các thao tác thêm, xóa hay sửa trên dòng DataRow. 4. Gọi phương thúc GetChanges() để lấy về một DataSet khác chứa tất cả các thay đổi trên dữ liệu. 5. Kiểm tra lỗi trên DataSet mới được tạo này bằng thuộc tính HasErrors. Nếu có lỗi thì ta sẽ tiến hành kiểm tra trên từng bảng DataTable của DataSet, khi gặp một bảng có lỗi thì ta tiếp tục dùng hàm GetErrors()để lấy về các dòng DataRow có lỗi, ứng với từng dòng ta sẽ dùng thuộc tính RowError trên dòng để xác định xem dòng đó có lỗi hay không để có thể đưa ra xử lý thích hợp. 6. Trộn hai DataSet lại thành một. 7. Gọi phương thức Update() của đối tượng DataAdapter với đối số truyền vào là DataSet vừa có trong thao tác trộn ở trên để cập nhật các thay đổi vào cơ sở dữ liệu. 8. Gọi phương thức AcceptChanges() của DataSet để cập nhật các thay đổi vào DataSet này hay phương thức RejectChanges() nếu từ chối cập nhật thay đổi cho DataSet hiện hành. Với luồng công việc trên, cho phép ta có thể kiểm soát tốt được việc thay đổi trên cơ sở dữ liệu hay việc gỡ lỗi cũng thuận tiện hơn. Trong ví dụ dưới đây , ta sẽ cho hiện thị dữ liệu trong bảng Customers lên một ListBox, sau đó ta tiến hành các thao tác thêm, xóa hay sửa trên cơ sở dữ liệu. Để dễ hiểu, ta giảm bớt một số thao tác quản lý ngoại lệ hay lỗi, chỉ tập trung vào mục đích chính của ta. Giao diện chính của ứng dụng sau khi hoàn chỉnh: Trong Form này, ta có một ListBox lbCustomers liệt kê các khách hàng, một Button btnUpdate cho việc cập nhật dữ liệu, một Button Xóa, ứng với nút thêm mới ta có tám hộp thoại TextBox để nhận dữ liệu gõ vào từ người dùng. Đồng thời ta có thêm một lblMessage để hiển thị các thông báo ứng với các thao tác trên. Bài tập thực hành Chuyên đề Visual Studio .NET 123
  • 124. 3.9 Truy cập và hiển thị dữ liệu Ta sẽ tạo ra ba biến thành viên: DataAdapter, DataSet và Command: private SqlDataAdapter DataAdapter; private DataSet DataSet; private DataTable dataTable; Việc khai báo các biến thành viên như vậy sẽ giúp ta có thể dùng lại cho các phương thức khác nhau. Ta khai báo chuỗi kết nối và truy vấn: string connectionString = "server=localhost; uid=sa; pwd=; database=northwind"; string commandString = "Select * from Customers"; Các chuỗi được dùng làm đối số để tạo đối tượng DataAdapter: DataAdapter=new SqlDataAdapter(commandString,ConnectionString); Tạo ra đối tượng DataSet mới, sau đó đẩy dữ liệu từ DataAdapter vào cho nó: DataSet = new DataSet(); DataAdapter.Fill(DataSet,"Customers"); Để hiển thị dữ liệu, ta sẽ gọi hàm PopulateDB()để đẩy dữ liệu vào ListBox: dataTable = DataSet.Tables[0]; lbCustomers.Items.Clear( ); foreach (DataRow dataRow in dataTable.Rows) { lbCustomers.Items.Add(dataRow["CompanyName"] + " (" + dataRow["ContactName"] + ")" ); } 3.10 Cập nhật một dòng dữ liệu Khi người dùng nhấn Button Update (cập nhật), ta sẽ lấy chỉ mục được chọn trên ListBox, và lấy ra dòng dữ liệu DataRow trong bảng ứng với chỉ mục trên. Sau đó cập nhật DataSet với dòng dữ liệu mới này nếu sau khi kiểm tra thấy chúng không có lỗi nào cả. Chi tiết về quá trình thực hiện cập nhật: Đầu tiên ta sẽ lấy về dòng dữ liệu người dùng muốn thay đổi từ đối tượng dataTable mà ta đã khai báo làm biến thành viên ngay từ đầu: DataRow targetRow = dataTable.Rows[lbCustomers.SelectedIndex]; Bài tập thực hành Chuyên đề Visual Studio .NET 124
  • 125. Hiển thị chuỗi thông báo cập nhật dòng dữ liệu đó cho người dùng biết. Để làm điều này ta sẽ gọi phương thức tình DoEvents() của đối tượng Application, hàm này sẽ giúp sơn mới lại màn hình với thông điệp hay các thay đổi khác. lblMessage.Text = "Updating " + targetRow["CompanyName"]; Application.DoEvents(); Gọi hàm BeginEdit() của đối tượng DataRow, để chuyển dòng dữ liệu sang chế độ hiệu chỉnh ( Edit ) và EndEdit()để kết thúc chế độ hiệu chỉnh dòng. targetRow.BeginEdit(); targetRow["CompanyName"] = txtCustomerName.Text; targetRow.EndEdit(); Lấy về các thay đổi trên đối tượng DataSet để kiểm tra xem các thay đổi có xảy ra bất kỳ lỗi nào không. Ở đây ta sẽ dùng một biến cờ có kiểu true/false để xác định là có lỗi là true, không có lỗi là false.Kiểm tra lỗi bằng cách dùng hai vòng lặp tuần tự trên bảng và dòng của DataSet mới lấy về ở trên, ta dùng thuộc tính HasErrors để kiểm tra lỗi trên bảng, phương thức GetErrors()để lấy về các dòng có lỗi trong bảng. DataSet DataSetChanged; DataSetChanged = DataSet.GetChanges(DataRowState.Modified); bool okayFlag = true; if (DataSetChanged.HasErrors) { okayFlag = false; string msg = "Error in row with customer ID "; foreach (DataTable theTable in DataSetChanged.Tables) { if (theTable.HasErrors) { DataRow[] errorRows = theTable.GetErrors( ); foreach (DataRow theRow in errorRows) msg = msg + theRow["CustomerID"]; } } Bài tập thực hành Chuyên đề Visual Studio .NET 125
  • 126. lblMessage.Text = msg; } Nếu biến cờ okagFlag là true,thì ta sẽ trộn DataSet ban đầu với DataSet thay đổi thành một, sau đó cập nhật DataSet sau khi trộn này vào cơ sở dữ liệu. if (okayFlag) { DataSet.Merge(DataSetChanged); DataAdapter.Update(DataSet,"Customers"); Tiếp theo hiển thị câu lệnh truy vấn cho người dùng biết, và cập nhật những thay đổi cho DataSet đầu tiên, rồi hiển thị dữ liệu mới lên đối tượng ListBox. lblMessage.Text = DataAdapter.UpdateCommand.CommandText; Application.DoEvents( ); DataSet.AcceptChanges( ); PopulateLB( ); Nếu cờ okayFlag là false, có nghĩa là có lỗi trong quá trình hiệu chỉnh dữ liệu, ta sẽ từ chối các thay đổi trên DataSet. else DataSet.RejectChanges( ); 3.11 Xóa một dòng dữ liệu Mã thực thi của sự kiện xóa thì đơn giản hơn một chút, ta nhận về dòng cần xóa: DataRow targetRow = dataTable.Rows[lbCustomers.SelectedIndex]; Giữ lại dòng cần xóa để dùng làm thông điệp hiển thị cho người dùng biết trước khi xóa dòng này khỏi cơ sở dữ liệu. string msg = targetRow["CompanyName"] + " deleted. ";, Bắt đầu thực hiện xóa trên bảng dữ liệu, cập nhật thay đổi vào DataSet và cập nhật luôn vào cơ sở dữ liệu: dataTable.Rows[lbCustomers.SelectedIndex].Delete( ); DataSet.AcceptChanges( ); DataAdapter.Update(DataSet,"Customers"); Khi gọi hàm AccceptChanges()để cập nhật thay đổi cho DataSet thì nó sẽ lần lượt gọi hàm này cho các DataTable, sau đó cho các DataRow để cập nhật chúng. Ta cũng cần Bài tập thực hành Chuyên đề Visual Studio .NET 126
  • 127. chú ý khi gọi hàm xóa trên bảng Customers, dòng dữ liệu DataRow của khách hàng này chỉ được xóa nếu nó không vi phạm ràng buộc trên các bảng khác, ở đây khách hàng chỉ được xóa nếu nếu khách hàng không có một hóa đơn nào trên bảng Orders. Nếu có ta phải tiến hành xóa trên bảng hóa đơn trước, sau đó mới xóa trên bảng Customers. 3.12 Tạo một dòng dữ liệu mới Sau khi người dùng cung cấp các thông tin về khách hàng cần tạo mới và nhấn Button tạo mới ( New ), ta sẽ viết mã thực thi trong hàm bắt sự kiện nhấn nút tạo mới này. Đầu tiên ta sẽ tạo ra một dòng mới trên đối tượng DataTable, sau đó gán dữ liệu trên các TextBox cho các cột của dòng mới này: DataRow newRow = dataTable.NewRow( ); newRow["CustomerID"] = txtCompanyID.Text; newRow["CompanyName"] = txtCompanyName.Text; newRow["ContactName"] = txtContactName.Text; newRow["ContactTitle"] = txtContactTitle.Text; newRow["Address"] = txtAddress.Text; newRow["City"] = txtCity.Text; newRow["PostalCode"] = txtZip.Text; newRow["Phone"] = txtPhone.Text; Thêm dòng mới với dữ liệu vào bảng DataTable, cập nhật vào cơ sở dữ liệu, hiển thị câu truy vấn, cập nhật DataSet, hiển thị dữ liệu mới lên hộp ListBox. Làm trắng các điều khiển TextBox bằng hàm thành viên ClearFields(). dataTable.Rows.Add(newRow); DataAdapter.Update(DataSet,"Customers"); lblMessage.Text = DataAdapter.UpdateCommand.CommandText; Application.DoEvents( ); DataSet.AcceptChanges( ); PopulateLB( ); ClearFields( ); Để hiểu rõ hoàn chỉnh ứng, ta sẽ xem mã hoàn chỉnh của toàn ứng dụng: using System; using System.Drawing; Bài tập thực hành Chuyên đề Visual Studio .NET 127
  • 128. using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.SqlClient; namespace ProgrammingCSharpWindows.Form { public class ADOForm1: System.Windows.Forms.Form { private System.ComponentModel.Container components; private System.Windows.Forms.Label label9; private System.Windows.Forms.TextBox txtPhone; private System.Windows.Forms.Label label8; private System.Windows.Forms.TextBox txtContactTitle; private System.Windows.Forms.Label label7; private System.Windows.Forms.TextBox txtZip; private System.Windows.Forms.Label label6; private System.Windows.Forms.TextBox txtCity; private System.Windows.Forms.Label label5; private System.Windows.Forms.TextBox txtAddress; private System.Windows.Forms.Label label4; private System.Windows.Forms.TextBox txtContactName; private System.Windows.Forms.Label label3; private System.Windows.Forms.TextBox txtCompanyName; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox txtCompanyID; private System.Windows.Forms.Label label1; private System.Windows.Forms.Button btnNew; private System.Windows.Forms.TextBox txtCustomerName; private System.Windows.Forms.Button btnUpdate; Bài tập thực hành Chuyên đề Visual Studio .NET 128
  • 129. private System.Windows.Forms.Label lblMessage; private System.Windows.Forms.Button btnDelete; private System.Windows.Forms.ListBox lbCustomers; private SqlDataAdapter DataAdapter; // biết thành viên DataSet và dataTable cho phép ta sử // dụng trên nhiều hàm khác nhau private DataSet DataSet; private DataTable dataTable; public ADOForm1( ) { InitializeComponent( ); string connectionString = "server=Neptune; uid=sa;" + " pwd=oWenmEany; database=northwind"; string commandString = "Select * from Customers"; DataAdapter = new SqlDataAdapter(commandString, connectionString); DataSet = new DataSet( ); DataAdapter.Fill(DataSet,"Customers"); PopulateLB( ); } // Đẩy dữ liệu vào điều khiển ListBox private void PopulateLB( ) { dataTable = DataSet.Tables[0]; lbCustomers.Items.Clear( ); foreach (DataRow dataRow in dataTable.Rows) { lbCustomers.Items.Add( dataRow["CompanyName"] + " (" + dataRow["ContactName"] + ")" ); } Bài tập thực hành Chuyên đề Visual Studio .NET 129
  • 130. } public override void Dispose( ) { base.Dispose( ); components.Dispose( ); } private void InitializeComponent( ) { this.components = new System.ComponentModel.Container(); this.txtCustomerName=new System.Windows.Forms.TextBox(); this.txtCity = new System.Windows.Forms.TextBox(); this.txtCompanyID = new System.Windows.Forms.TextBox(); this.lblMessage = new System.Windows.Forms.Label(); this.btnUpdate = new System.Windows.Forms.Button(); this.txtContactName= new System.Windows.Forms.TextBox(); this.txtZip = new System.Windows.Forms.TextBox(); this.btnDelete = new System.Windows.Forms.Button(); this.txtContactTitle=new System.Windows.Forms.TextBox(); this.txtAddress = new System.Windows.Forms.TextBox(); this.txtCompanyName=new System.Windows.Forms.TextBox( ); this.label5 = new System.Windows.Forms.Label( ); this.label6 = new System.Windows.Forms.Label( ); this.label7 = new System.Windows.Forms.Label( ); this.label8 = new System.Windows.Forms.Label( ); this.label9 = new System.Windows.Forms.Label( ); this.label4 = new System.Windows.Forms.Label( ); this.lbCustomers = new System.Windows.Forms.ListBox( ); this.txtPhone = new System.Windows.Forms.TextBox( ); this.btnNew = new System.Windows.Forms.Button( ); this.label1 = new System.Windows.Forms.Label( ); Bài tập thực hành Chuyên đề Visual Studio .NET 130
  • 131. this.label2 = new System.Windows.Forms.Label( ); this.label3 = new System.Windows.Forms.Label( ); txtCustomerName.Location = new System.Drawing.Point(256, 120); txtCustomerName.TabIndex = 4; txtCustomerName.Size = new System.Drawing.Size(160, 20); txtCity.Location = new System.Drawing.Point(384, 245); txtCity.TabIndex = 15; txtCity.Size = new System.Drawing.Size (160, 20); txtCompanyID.Location = new System.Drawing.Point (136, 216); txtCompanyID.TabIndex = 7; txtCompanyID.Size = new System.Drawing.Size (160, 20); lblMessage.Location = new System.Drawing.Point(32, 368); lblMessage.Text = "Press New, Update or Delete"; lblMessage.Size = new System.Drawing.Size (416, 48); lblMessage.TabIndex = 1; btnUpdate.Location = new System.Drawing.Point (32, 120); btnUpdate.Size = new System.Drawing.Size (75, 23); btnUpdate.TabIndex = 0; btnUpdate.Text = "Update"; btnUpdate.Click += new System.EventHandler (this.btnUpdate_Click); txtContactName.Location = new System.Drawing.Point(136, 274); txtContactName.TabIndex = 11; txtContactName.Size = new System.Drawing.Size (160, 20); txtZip.Location = new System.Drawing.Point (384, 274); txtZip.TabIndex = 17; txtZip.Size = new System.Drawing.Size (160, 20); Bài tập thực hành Chuyên đề Visual Studio .NET 131
  • 132. btnDelete.Location = new System.Drawing.Point(472, 120); btnDelete.Size = new System.Drawing.Size(75, 23); btnDelete.TabIndex = 2; btnDelete.Text = "Delete"; btnDelete.Click += new System.EventHandler (this.btnDelete_Click); txtContactTitle.Location = new System.Drawing.Point(136, 303); txtContactTitle.TabIndex = 19; txtContactTitle.Size = new System.Drawing.Size(160, 20); txtAddress.Location = new System.Drawing.Point(384, 216); txtAddress.TabIndex = 13; txtAddress.Size = new System.Drawing.Size (160, 20); txtCompanyName.Location= new System.Drawing.Point (136, 245); txtCompanyName.TabIndex = 9; txtCompanyName.Size = new System.Drawing.Size (160, 20); label5.Location = new System.Drawing.Point (320, 252); label5.Text = "City"; label5.Size = new System.Drawing.Size (48, 16); label5.TabIndex = 14; label6.Location = new System.Drawing.Point (320, 284); label6.Text = "Zip"; label6.Size = new System.Drawing.Size (40, 16); label6.TabIndex = 16; label7.Location = new System.Drawing.Point (40, 312); label7.Text = "Contact Title"; label7.Size = new System.Drawing.Size (88, 16); label7.TabIndex = 18; label8.Location = new System.Drawing.Point (320, 312); label8.Text = "Phone"; Bài tập thực hành Chuyên đề Visual Studio .NET 132
  • 133. label8.Size = new System.Drawing.Size (56, 16); label8.TabIndex = 20; label9.Location = new System.Drawing.Point (120, 120); label9.Text = "New Customer Name:"; label9.Size = new System.Drawing.Size (120, 24); label9.TabIndex = 22; label4.Location = new System.Drawing.Point (320, 224); label4.Text = "Address"; label4.Size = new System.Drawing.Size (56, 16); label4.TabIndex = 12; lbCustomers.Location = new System.Drawing.Point(32, 16); lbCustomers.Size = new System.Drawing.Size (512, 95); lbCustomers.TabIndex = 3; txtPhone.Location = new System.Drawing.Point (384, 303); txtPhone.TabIndex = 21; txtPhone.Size = new System.Drawing.Size (160, 20); btnNew.Location = new System.Drawing.Point (472, 336); btnNew.Size = new System.Drawing.Size (75, 23); btnNew.TabIndex = 5; btnNew.Text = "New"; btnNew.Click += new System.EventHandler(this.btnNew_Click); label1.Location = new System.Drawing.Point (40, 224); label1.Text = "Company ID"; label1.Size = new System.Drawing.Size (88, 16); label1.TabIndex = 6; label2.Location = new System.Drawing.Point (40, 252); label2.Text = "Company Name"; label2.Size = new System.Drawing.Size (88, 16); label2.TabIndex = 8; label3.Location = new System.Drawing.Point (40, 284); Bài tập thực hành Chuyên đề Visual Studio .NET 133
  • 134. label3.Text = "Contact Name"; label3.Size = new System.Drawing.Size (88, 16); label3.TabIndex = 10; this.Text = "Customers Update Form"; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size (584, 421); this.Controls.Add (this.label9); this.Controls.Add (this.txtPhone); this.Controls.Add (this.label8); this.Controls.Add (this.txtContactTitle); this.Controls.Add (this.label7); this.Controls.Add (this.txtZip); this.Controls.Add (this.label6); this.Controls.Add (this.txtCity); this.Controls.Add (this.label5); this.Controls.Add (this.txtAddress); this.Controls.Add (this.label4); this.Controls.Add (this.txtContactName); this.Controls.Add (this.label3); this.Controls.Add (this.txtCompanyName); this.Controls.Add (this.label2); this.Controls.Add (this.txtCompanyID); this.Controls.Add (this.label1); this.Controls.Add (this.btnNew); this.Controls.Add (this.txtCustomerName); this.Controls.Add (this.btnUpdate); this.Controls.Add (this.lblMessage); this.Controls.Add (this.btnDelete); this.Controls.Add (this.lbCustomers); } Bài tập thực hành Chuyên đề Visual Studio .NET 134
  • 135. // Quản lý sự kiện nhấn nút tạo mới (New) protected void btnNew_Click( object sender, System.EventArgs e) { // tạo một dòng mới DataRow newRow = dataTable.NewRow( ); newRow["CustomerID"] = txtCompanyID.Text; newRow["CompanyName"] = txtCompanyName.Text; newRow["ContactName"] = txtContactName.Text; newRow["ContactTitle"] = txtContactTitle.Text; newRow["Address"] = txtAddress.Text; newRow["City"] = txtCity.Text; newRow["PostalCode"] = txtZip.Text; newRow["Phone"] = txtPhone.Text; // thêm một dòng mới vào bảng dataTable.Rows.Add(newRow); // cập nhật vào cơ sở dữ liệu DataAdapter.Update(DataSet,"Customers"); // thông báo cho người dùng biết câu truy vấn thay đổi lblMessage.Text = DataAdapter.UpdateCommand.CommandText; Application.DoEvents( ); DataSet.AcceptChanges( ); // hiển thị lại dữ liệu cho điều khiển ListBox PopulateLB( ); // Xoá trằng các TextBox ClearFields( ); } // Xóa trắng các TextBox private void ClearFields( ) { txtCompanyID.Text = ""; Bài tập thực hành Chuyên đề Visual Studio .NET 135
  • 136. txtCompanyName.Text = ""; txtContactName.Text = ""; txtContactTitle.Text = ""; txtAddress.Text = ""; txtCity.Text = ""; txtZip.Text = ""; txtPhone.Text = ""; } // quản lý sự kiện nhất nút chọn cập nhật (Update) protected void btnUpdate_Click( object sender, EventArgs e) { // lấy vể dòng được chọn trên ListBox DataRow targetRow = dataTable.Rows[lbCustomers.SelectedIndex]; // thông báo cho người biết dòng cập nhật lblMessage.Text = "Updating " + targetRow["CompanyName"]; Application.DoEvents( ); // hiệu chỉnh dòng targetRow.BeginEdit( ); targetRow["CompanyName"] = txtCustomerName.Text; targetRow.EndEdit( ); // lấy về___ các dòng thay đổi DataSet DataSetChanged = DataSet.GetChanges(DataRowState.Modified); // đảm bảo không có dòng nào có lỗi bool okayFlag = true; if (DataSetChanged.HasErrors) { okayFlag = false; string msg = "Error in row with customer ID "; Bài tập thực hành Chuyên đề Visual Studio .NET 136
  • 137. // kiểm tra lỗi trên từng bảng foreach (DataTable theTable in DataSetChanged.Tables) { // nếu bảng có lỗi thì tìm lỗi trên dòng cụ thể if (theTable.HasErrors) { // lấy các dòng có lỗi DataRow[] errorRows = theTable.GetErrors( ); // duyệt qua từng dòng có lỗi để thống báo. foreach (DataRow theRow in errorRows) { msg = msg + theRow["CustomerID"]; } } } lblMessage.Text = msg; } // nếu không có lỗi if (okayFlag) { // trộn các thay đổi trong 2 DataSet thành một DataSet.Merge(DataSetChanged); // cập nhật cơ sở dữ liệu DataAdapter.Update(DataSet,"Customers"); // thông báo câu truy vấn cho người dùng lblMessage.Text = DataAdapter.UpdateCommand.CommandText; Application.DoEvents( ); // cập nhật DataSet và // hiển thị dữ liệu mới cho ListBox DataSet.AcceptChanges( ); Bài tập thực hành Chuyên đề Visual Studio .NET 137
  • 138. PopulateLB( ); } else // nếu có lỗi DataSet.RejectChanges( ); } // quản lý sự kiện xóa protected void btnDelete_Click( object sender, EventArgs e) { // lấy về___ dòng được chọn trên ListBox DataRow targetRow = dataTable.Rows[lbCustomers.SelectedIndex]; // chuẩn bị thông báo cho người dùng string msg = targetRow["CompanyName"] + " deleted. "; // xóa dòng được chọn dataTable.Rows[lbCustomers.SelectedIndex].Delete( ); // cập nhật thay đổi cho DataSet DataSet.AcceptChanges( ); // cập nhật cơ sở dữ liệu DataAdapter.Update(DataSet,"Customers"); // hiển thị lại ListBox với dữ liệu thay đổi PopulateLB( ); // thông báo cho người dùng biết lblMessage.Text = msg; Application.DoEvents( ); } public static void Main(string[] args) { Application.Run(new ADOForm1( )); } } Bài tập thực hành Chuyên đề Visual Studio .NET 138
  • 139. } PHẦN 4 XÂY DỰNG ỨNG DUNG WEB VỚI WEBFORMS NG NG ̣ NG Công nghệ .NET được dùng để xây dựng các ứng dụng Web là ASP.NET, nó cung cấp hai vùng tên khá mạnh và đầy đủ phục vụ cho việc tạo các ứng dụng Web là System.Web và System.Web.UI. Trong phần này chúng ta sẽ tập trung chủ yếu vào việc dùng ngôn ngữ C# để lập trình với ASP.NET. Bộ công cụ Web Form cũng được thiết kế để hỗ trợ mô hình phát triển nhanh (RAD). Với Web Form, ta có thể kéo thả các điều khiển trên Form thiết kế cũng như có thể viết mã trực tiếp trong tập tin .aspx hay .aspx.cs. Ứng dụng Web sẽ được triển khai trên máy chủ, còn người dùng sẽ tương tác với ứng dụng thông qua trình duyệt. .NET còn hỗ trợ ta bộ cung cụ để tạo ra các ứng dụng tuân theo mô hình n - lớp (tầng - n tier), giúp ta có thể quản lý được ứng dụng được dễ dàng hơn và nhờ thế nâng cao hiệu suất phát triển phần mềm. 4.1 Tìm hiểu về Web Forms Web Form là bộ công cụ cho phép thực thi các ứng dụng mà các trang Web do nó tạo động ra được phân phối đến trình duyệt thông qua mạng Internet. Với Web Forms, ta tạo ra các trang HTML với nội dung tĩnh và dùng mã C# chạy trên Server để xử lý dữ liệu tĩnh này rồi tạo ra trang Web động, gửi trang này về trình duyệt dưới mã HTML chuẩn. Web Forms được thiết để chạy trên bất kỳ trình duyệt nào, trang HTML gửi về sẽ được gọt giũa sao cho thích hợp với phiên bản của trình duyệt. Ngoài dùng C#, ta cũng có thể dùng ngôn ngữ VB.NET để tạo ra các ứng dụng Web tương tự. Web Forms chia giao diện người dùng thành hai phần: phần thấy trực quan ( hay UI ) và phần trang mã phía sau của UI. Quan điểm này thì tương tự với Windows Form, nhưng với Web Forms, hai phần này nằm trên hai tập tin riêng biệt. Phần giao diện UI được lưu trữ trong tập tin có phần mở rộng là .aspx, còn mã được lưu trữ trong tập tin có phần mở rộng là .aspx.cs. Với môi trường làm việc được cung cấp bởi bộ Visual Studio .NET, tạo các ứng dụng Web đơn giản chỉ là mở Form mới, kéo thả và viết mả quản lý sự kiện thích hợp. Web Forms được tích hợp thêm một loạt các điều khiển thực thi trên Server, có thể tự kiểm tra sự hợp lệ của dữ liệu ngay trên máy khách mà ta không phải viết mã mô tả gì cà. 4.2 Các sự kiện của Web Forms Một sự kiện (Events) được tạo ra khi người dùng nhấn chọn một Button, chọn một mục trong ListBox hay thực hiện một thao tác nào đó trên UI. Các sự kiện cũng có thể được phát sinh hệ thống bắt đầu hay kết thúc. Phương thức đáp ứng sự kiện gọi là trình quản lý sự kiện, các trình quản lý sự kiện này được viết bằng mã C# trong trang mã (code-behind) và kết hợp với các thuộc tính của các điều khiển thuộc trang. Bài tập thực hành Chuyên đề Visual Studio .NET 139
  • 140. Trình quản lý sự kiện là một “Delegate”, phương thức này sẽ trả về kiểu void, và có hai đối số. Đối số đầu tiên là thể hiện của đối tượng phát sinh ra sự kiện, đối số thứ hai là đối tượng EventArg hay một đối tượng khác được dẫn xuất từ đối tượng EventArgs. Các sự kiện này được quản lý trên Server. 4.2.1 Sự kiện PostBack và Non-PostBack PostBack là sự kiện sẽ khiến Form được gửi về Server ngay lập tức, chẳng hạn sự kiện đệ trình một Form với phương thức Post. Đối lập với PostBack là Non- PostBack, sự kiện này không gửi Form nên Server mà nó lưu sự kiện trên vùng nhớ Cache cho tới khi có một sự kiện PostBack nữa xảy ra. Khi một điều khiển có thuộc tính AutoPostBack là true thì sự kiện PostBack sẽ có tác dụng trên điều khiển đó:mặc nhiên thuộc tính AutoPostBach của điều khiển DropDownList là false,ta phải đặt lại là true thì sự kiện chọn một mục khác trong DropDownList này mới có tác dụng. 4.2.2 Trạng thái của ứng dụng Web (State) Trạng thái của ứng dụng Web là giá trị hiện hành của các điều khiển và mọi biến trong phiên làm việc hiện hành của người dùng. Web là môi trường không trạng thái, nghĩa là mỗi sự kiện Post lên Server đều làm mất đi mọi thông tin về phiên làm việc trước đó. Tuy nhiên ASP.NET đã cung cấp cơ chế hỗ trợ việc duy trì trạng thái về phiên của người dùng. Bất kỳ trang nào khi được gửi lên máy chủ Server đều được máy chủ tổng hợp thông tin và tái tạo lại sau đó mới gửi xuống trình duyệt cho máy khách. ASP.NET cung cấp một cơ chế giúp duy trì trạng thái của các điều khiển phía máy chủ (Server Control ) một cách tự động. Vì thế nếu ta cung cấp cho người dùng một danh sách dữ liệu ListBox, và người dùng thực hiện việc chọn lựa trên ListBox này, sự kiện chọn lựa này sẽ vẫn được duy trì sau khi trang được gửi lên máy chủ và gửi về cho trình duyệt cho máy khách. 4.2.3 Chu trình sống của một Web-Form Khi có yêu cầu một trang Web trên máy chủ Web sẽ tạo ra một chuỗi các sự kiện ở máy chủ đó, từ lúc bắt đầu cho đến lúc kết thúc một yêu cầu sẽ hình thành một chu trình sống ( Life-Cycle ) cho trang Web và các thành phần thuộc nó. Khi một trang Web được yêu cầu, máy chủ sẽ tiến hành mở ( Load ) nó và khi hoàn tất yêu cầu máy chủ sẽ đóng trang này lại, kết xuất của yêu cầu này là một trang HTML tương ứng sẽ được gửi về cho trình duyệt. Dưới đây sẽ liệt kê một số sự kiện, ta có thể bắt các sự kiện để xử lý thích hợp hay bỏ qua để ASP.NET xử lý mặc định. Khởi tạo (Initialize) Là sự kiện đầu tiên trong chu trình sống của trang, ta có thể khởi bất kỳ các thông số cho trang hay các điều khiển thuộc trang. Mở trạng thái vùng quan sát (Load View State) Được gọi khi thuộc tính ViewState của điều khiển được công bố hay gọ. Các giá trị trong ViewState sẽ được lưu trữ trong một biến ẩn ( Hidden Field ), ta có thể lấy giá trị này thông qua hàm Bài tập thực hành Chuyên đề Visual Studio .NET 140
  • 141. LoadViewState() hay lấy trực tiếp. Kết thúc (Dispose) Ta có thể dùng sự kiện này để giải phóng bất kỳ tài nguyên nguyên nào: bộ nhớ hay hủy bỏ các kết nối đến cơ sở dữ liệu. Ví dụ: Hiển thị chuỗi lên trang Đầu tiên ta cần chạy Visual Studio .NET, sau đó tạo một dự án mới kiểu WebApplication, ngôn ngữ chọn là C# và ứng dụng sẽ có tên là ProgrammingCSharpWeb.Url mặc nhiên của ứng dụng sẽ có tên là http://localhost/ ProgrammingCSharpWeb. Visual Studio .NET sẽ đặt hầu hết các tập tin nó tạo ra cho ứng dụng trong thư mụcWeb mặc định trên máy người dùng, ví dụ: D:InetpubwwwrootProgrammingCSharpWeb. Trong .NET, một giải pháp (Solution) có một hay hiều dự án (Project), mỗi dự án sẽ tạo ra một thư viện liên kết động (DLL) hay tập tin thực thi (EXE). Để có thể chạy được ứng dụng Web Form, ta cần phải cài đặt IIS và FrontPage Server Extension trên máy tính. Khi ứng dụng Web Form được tạo, .NET tạo sẵn một số tập tin và một trang Web có tên mặc định là WebForm1.aspx chỉ chứa mã HTML và WebForm1.cs chứa mã quản lý trang. Trang mã .cs không nằm trong cửa sổ Solution Explorer, để hiển thị nó ta chọn ProjectShow All Files, ta chỉ cần nhấn đúp chuột trái trên trang Web là cửa sổ soạn thảo mã (Editor) sẽ hiện nên, cho phép ta viết mã quản lý trang. Để chuyển từ cửa số thiết kế kéo thả sang cửa sổ mã HTML của trang, ta chọn hai Tab ở góc bên trái phía dưới màn hình. Đặt tên lại cho trang Web bằng cách nhấn chuột phải lên trang và chọn mục Rename để đổi tên trang thành HelloWeb.aspx, .NET cũng sẽ tự động đổi tên trang mã của trang thành HelloWeb.cs. NET cũng tạo ra một số mã HTML cho trang: .NET đã phát sinh ra một số mã ASP.NET: <%@ Page language="c#" Codebehind="HelloWeb.cs" AutoEventWireup="false" Inherits="ProgrammingCSharpWeb.WebForm1" %> Thuộc tính language chỉ ra ngôn ngữ lập trình được dùng trong trang mã để quản lý trang, ở đây là C#. Codebehide xác định trang mã quản lý có tên HelloWeb.cs và thuộc tính Inherits chỉ trang Web được thừa kế từ lớp WebForm1 được viết trong HelloWeb.cs: public class WebForm1: System.Web.UI.Page Ta thấy trang này được thừa kế từ lớp System.Web.UI.Page, lớp này do ASP.NET cung cấp, xác định các thuộc tính, phương thức và các sự kiện chung cho các trang phía máy chủ. Mã HTML phát sinh định dạng thuộc tính của Form: Bài tập thực hành Chuyên đề Visual Studio .NET 141
  • 142. <form id="Form1" method="post" runat="server"> Thuộc tính id làm định danh cho Form, thuộc tính method có giá trị là “POST” nghĩa là Form sẽ được gởi lên máy chủ ngay lập tức khi nhận một sự kiện do người dùng phát ra ( như sự kiện nhấn nút ) và cờ IsPostBack trên máy chủ khi đó sẽ có giá trị là true. Biến cờ này có giá trị là false nếu Form được đệ trình với phương thức “GET” hay lần đầu tiên trang được gọi. Bất kỳ điều khiển nào hay Form có thuộc tính runat=”server” thì điều khiển hay Form này sẽ được xử lý bởi ASP.NET Framework trên máy chủ. Thuộc tính MS_POSITIONING =“GridLayout” trong thẻ <Body>, cho biết cách bố trí các điều khiển trên Form theodạng lưới, ngoài ra ta còn có thể bố trí các điều khiển trôi lổi trên trang, bằng cáchgán thuộc tính MS_POSITIONING thành “FlowLayout”. Hiện giờ Form của ta là trống, để hiển thị một chuỗi gì đó lên màn hình, ta gõ dòng mã sau trong thẻ <body>: Hello World! It is now <% = DateTime.Now.ToString( ) %> Giống với ASP, phần nằm trong dấu <% %> được xem như là mã quản lý cho trang, ở đây là mã C#. Dấu = chỉ ra một giá trị nhận được từ một biến hay một đối tượng nào đó, ta cũng có thể viết mã trên lại như sau với cùng chức năng: Hello World! It is now <% Response.Write(DateTime.Now.ToString( )); %> Thực thi trang này ( Ctrl-F5 ), kết quả sẽ hiện trên trình duyệt như sau: Để thêm các điều khiển cho trang, hoặc là ta có thể viết mã trong của sổ HTML hoặc là kéo thả các điều khiển trên bộ công của Web Form vào cửa sổ thiết kế trang. ASP.NET sẽ tự động phát sinh ra kết quả từ mã HTML thành các điều khiển cũng như từ các điều khiển trên trang thiết thành mã HTML tương ứng. Ví dụ, kéo hai RadioButton vào trang và gán cùng một giá trị nào đó cho thuộc tính GroupName của cả hai điều khiển, thuộc tính này sẽ làm cho các nút chọn loại trừ lẫn nhau. Mã HTML của trang trong thẻ <Form> do ASP.NET phát sinh sẽ như sau: Các điều khiển của ASP.NET, có thêm chữ “asp:” phía trước tên của điều khiển đó, được thiết kế mang tính hướng đối tượng nhiều hơn. <asp:RadioButton> <asp:CheckBox> <asp:Button> <asp:TextBox rows="1"> <asp:TextBox rows="5"> Bài tập thực hành Chuyên đề Visual Studio .NET 142
  • 143. Ngoài các điều khiển của ASP.NET, các điều khiển HTML chuẩn cũng được ASP.NET hỗ trợ. Tuy nhiên các điều khiển không tạo sự dễ đọc trong mã nguồn do tính đối tượng trên chúng không rõ ràng, các điều khiển HTML chuẩn ứng với năm điều khiển trên là: <input type = "radio"> <input type="checkbox"> <input type="button"> <input type="text"> <textarea> 4.3 Điều khiển xác nhận hợp ASP.NET cung cấp một tập các điều khiển xác nhận hợp lệ dữ liệu nhập phía máychủ cũng như ở dưới trình duyệt của máy khách. Tuy nhiên việc xác nhận hợp lệ dưới máy khách chỉ là một chọn lựa, ta có thể tắt nó đi, nhưng việc xác nhận hợp lệ trên máy chủ thông qua các điều khiển này là bắt buộc, nhằm phòng ngừa một số trường hợp dữ liệu nhập là giả mạo. Việc kiểm tra hợp lệ của mã trên máy chủ là đề phòng các trường hợp. Một số loại xác nhận hợp lệ: dữ liệu không được rỗng, thỏa một định dạng dữ liệu nào đó … Các điều khiển xác nhận hợp lệ phải được gắn liền với một điều khiển nhận dữ liệuHTML nào đó, các điều khiển nhập được liệt trong bảng sau: Ứng với một điều khiển nhập HTML, ta có thể gắn nhiều điều khiển xác nhận hợplệ cho nó, bảng dưới đây sẽ liệt kê các điều khiển nhập hiện có: CompareValidator So sánh các giá trị của hai điều khiển để xem có bằng nhau hay không CustomValidator Gọi một hàm do người dùng định nghĩa để thi hành việc kiểm tra RangeValidator Kiểm tra xem một mục có nằm trong một miền đã cho hay không RegularExpressionvalidator Kiểm tra người dùng có sửa đổi một mục ( mà giá trị của nó khác với một giá trị đã chỉ định ban đầu, ngầm định giá trị ban đầu là một chuỗi trống ) hay không ValidationSummary Thông báo sự hợp lệ trên các điều khiển 4.4 Một số ví dụ mẫu minh họa Một cách thuận tiện nhất để học một công nghệ mới chính là dựa vào các ví dụ, vìvậy trong phần này chúng ta sẽ khảo sát một vài ví dụ để minh họa cho phần lý thuyết của chúng ta. Như ta đã biết, ta có thể viết mã quản lý theo hai cách: hoặc là viết trong tập Bài tập thực hành Chuyên đề Visual Studio .NET 143
  • 144. tin .cs hoặc là viết trực tiếp trong trang chứa mã HTML. Ở đây để dễ tập trung vào các ví dụ của chúng ta, ta sẽ viết mã quản lý trực tiếp trên trang HTML. Kết buộc dữ liệu Không thông qua thuộc tính DataSource Ứng dụng của chúng ta đơn giản chỉ hiện lên trang tên khách hàng và số hóa đơn bằng cách dùng hàm DataBind(). Hàm này sẽ kết buộc dữ liệu của mọi thuộc tính hay của bất kỳ đối tượng. <html> <head> // mã quản lý C# sẽ___ được viết trong thẻ <script> này <script language="C#" runat="server"> // trang sẽ___ gọi hàm này đầu tiên, ta sẽ___ thực hiện kết buộc // trực tiếp trong hàm này void Page_Load(Object sender, EventArgs e) { Page.DataBind(); } // lấy giá trị của thuộc tính thông qua thuộc tính // get string custID{ get { return "ALFKI"; } } int orderCount{ get { return 11; } } </script> </head> <body> <h3><font face="Verdana"> Ket buoc khong dung DataSource Bài tập thực hành Chuyên đề Visual Studio .NET 144
  • 145. </font></h3> <form runat=server> Khach hang: <b><%# custID %></b><br> So hoa don: <b><%# orderCount %></b> </form> </body> </html> Điều khiển DataList với DataSource Trong ví dụ này, ta sẽ dùng thuộc tính DataSource của điều khiển <asp:DataList> để kết buộc dữ liệu, ta sẽ cung cấp cho thuộc tính DataSource này một bảng dữ liệu giả, sau đó dùng hàm DataBinder.Eval()để kết buộc dữ liệu trong DataSource theo một định dạng ( Format ) thích hợp mong muốn. Dữ liệu sẽ được hiển thị lên màn hình dưới dạng một bảng các hóa đơn sau khi ta gọi hàm DataBind(). //Không gian tên chứa các đối tượng của ADO.NET <%@ Import namespace="System.Data" %> <html> <head> <script language="C#" runat="server"> void Page_Load(Object sender, EventArgs e) { // nếu trang được gọi lần đầu tiên if (!Page.IsPostBack) { // tạo ra một bảng dữ liệu mới gồm 4 cột , sau đó thêm dữ // liệu giả cho bảng DataTable dt = new DataTable(); DataRow dr; // thêm 4 cột DataColumn vào bảng, mỗi cột có các // kiểu dữ liệu riêng dt.Columns.Add(new DataColumn("IntegerValue", typeof(Int32))); dt.Columns.Add(new DataColumn("StringValue", typeof(string))); dt.Columns.Add(new DataColumn("DateTimeValue", typeof(DateTime))); dt.Columns.Add(new DataColumn("BoolValue", typeof(bool))); Bài tập thực hành Chuyên đề Visual Studio .NET 145
  • 146. // thêm 9 dòng dữ liệu cho bảng bằng cách tạo ra // một dòng mới dùng phương thức NewRow() của đối // tượng DataTable, sau đó gán dữ liệu giả cho // dòng này và thêm dòng dữ liệu này vào bảng for (int i = 0; i < 9; i++) { dr = dt.NewRow(); dr[0] = i; dr[1] = "Item " + i.ToString(); dr[2] = DateTime.Now; dr[3] = (i % 2 != 0) ? true: false; dt.Rows.Add(dr); } // gán bảng dữ liệu cho thuộc tính DataSource của điều // khiển DataList, sau đó thực hiện kết buộc bằng hàm // DataBind() dataList1.DataSource = new DataView(dt); dataList1.DataBind(); } } </script> </head> <body> <h3><font face="Verdana">Ket buoc du lieu dung DataSource thong qua ham DataBind.Eval() </font></h3> <form runat=server> // điều khiển danh sách cho phép ta kết buộc dữ liệu khá // linh động, ta chỉ cần cung cấp cho nó một DataSource // thích hợp, sau đó gọi hàm DataBind()để hiển thị dữ liệu // lên trang <asp:DataList id="dataList1" runat="server" Bài tập thực hành Chuyên đề Visual Studio .NET 146
  • 147. RepeatColumns="3" Width="80%" BorderColor="black" BorderWidth="1" GridLines="Both" CellPadding="4" CellSpacing="0"> // đây là một thuộc tính của lưới, khi gọi hàm // DabaBind(), dữ liệu trong DataSource sẽ___ được trích ra // (nếu là danh các đối tượng thì mỗi lần trích sẽ___ lấy ra // một phần tử kiểu đối tượng đó, sau đó dùng hàm // DataBinder.Eval()để gán giá trị, còn nếu là một bảng // dữ liệu thì mỗi lần kết buộc sẽ___ lấy ra một dòng dữ // liệu, hàm DataBind.Eval() sẽ___ lấy dữ liệu của từng // trường) để kết buộc lên trang. Nó sẽ___ lặp lại thao tác // này cho tới khi dữ liệu được kết buộc hết. <ItemTemplate> //lấy dữ liệu trên cột đầu tiên để kết buộc Ngay hoa don: <%# DataBinder.Eval(Container.DataItem, "DateTimeValue", "{0:d}") %> //lấy dữ liệu trên cốt thứ 2 So luong: <%# DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:N2}") %> //cột thứ 3 Muc: <%# DataBinder.Eval(Container.DataItem, "StringValue") %> //cột thứ 4 Ngay hoa don: <asp:CheckBox id=chk1 Checked='<%# (bool)DataBinder.Eval(Container.DataItem, "BoolValue") %>' runat=server/><p> </ItemTemplate> Bài tập thực hành Chuyên đề Visual Studio .NET 147
  • 148. </asp:Datalist> </form> </body> </html> Kết buộc với điều khiển DataGrid Trong ví trước, ta đã tìm hiểu sơ qua về cách đẩy dữ liệu vào thuộc tính DataSource của điều khiển DataList thông qua hàm kết buộc DataBind().Ví dụ này chúng ta sẽ khảo sát thêm về cách kết buộc trên điều khiển lưới DataGrid và cách dùng điều khiển xác nhận hợp lệ trên dữ liệu. Khi ứng dụng chạy sẽ hiển thị một bảng dữ liệu lên trang, người dùng có thể hiệu chỉnh bất kỳ một dòng nào trên bảng dữ liệu bằng cách nhấn vào chuỗi lệnh hiệu chỉnh ( Edit ) trên lưới, gõ vào các dữ liệu cần hiệu chỉnh, khi muốn hủy bỏ thao tác hiệu chỉnh ta nhấn chọn chuỗi bỏ qua (Cancel). Để tập trung vào mục đích của ví dụ, chúng ta sẽ dùng bảng dữ liệu giả, cách làm sẽ tương tự trên bảng dữ liệu lấy ra từ cơ sở dữ liệu. Sau đây là mã của ví dụ: //không gian tên cần thiết để truy cập đến các đối tương ADO.NET <%@ Import Namespace="System.Data" %> <html> <script language="C#" runat="server"> //khai báo đối tượng bảng và khung nhìn DataTable Cart; DataView CartView; // lấy dữ liệu trong Session, nếu không có thì ta sẽ___ tạo ra một // bảng dữ liệu khác void Page_Load(Object sender, EventArgs e) { if (Session["DG6_ShoppingCart"] == null) { Cart = new DataTable(); //bảng sẽ___ có 3 cột đều có kiểu là chuỗi Cart.Columns.Add(new DataColumn("Qty", typeof(string))); Cart.Columns.Add(new DataColumn("Item", typeof(string))); Cart.Columns.Add(new DataColumn("Price", typeof(string))); //đẩy định danh của bảng vào phiên làm việc hiện thời Session["DG6_ShoppingCart"] = Cart; Bài tập thực hành Chuyên đề Visual Studio .NET 148
  • 149. // tạo dữ liệu mẫu cho bảng for (int i=1; i<5; i++) { DataRow dr = Cart.NewRow(); dr[0] = ((int)(i%2)+1).ToString(); dr[1] = "Item " + i.ToString(); dr[2] = ((double)(1.23 * (i+1))).ToString(); Cart.Rows.Add(dr); } } else { //nếu bảng đã có sẵn trong Session, ta sẽ___ lấy ra dùng Cart = (DataTable)Session["DG6_ShoppingCart"]; } // tạo ra khung nhìn cho bảng, sau đó sắp xếp khung nhìn theo // cột Item CartView = new DataView(Cart); CartView.Sort = "Item"; // nếu trang được gọi lần đầu tiên thì kết buộc dữ liệu thông // qua hàm BindGrid()của ta if (!IsPostBack) { BindGrid(); } } // sự kiện nhấn chuỗi hiệu chỉnh (Edit) trên lưới, ta sẽ___ lấy chỉ // mục của dòng cần hiệu chỉnh thông qua đối tượng // DataGridCommandEventArgs, sau đó truyền chỉ mục này cho điều // khiển lưới của ta và gọi hàm kết buộc của ta để đẩy dữ liệu // lên lưới public void MyDataGrid_Edit(Object sender, DataGridCommandEventArgs e) { Bài tập thực hành Chuyên đề Visual Studio .NET 149
  • 150. MyDataGrid.EditItemIndex = (int)e.Item.ItemIndex; BindGrid(); } //sự kiện nhấn bỏ qua trên lưới (Cancel) public void MyDataGrid_Cancel(Object sender, DataGridCommandEventArgs e) { MyDataGrid.EditItemIndex = -1; BindGrid(); } //sau khi hiệu chỉnh dữ liệu, người dùng tiến hành cập nhật public void MyDataGrid_Update(Object sender, DataGridCommandEventArgs e) { // lấy dữ liệu trên TextBox string item = e.Item.Cells[1].Text; string qty = ((TextBox)e.Item.Cells[2].Controls[0]).Text; string price = ((TextBox)e.Item.Cells[3].Controls[0]).Text; // Ở đây, do chúng ta dùng dữ liệu giả lưu trên bộ nhớ chính, // nếu dùng cơ sở dữ liệu thì chúng ta sẽ___ tiến hành hiệu chỉnh // trực tiếp trong cơ sở dữ liệu bằng các câu truy vấn: // UPDATE, SELECT, DELETE //xóa dòng cũ CartView.RowFilter = "Item='"+item+"'"; if (CartView.Count > 0) { CartView.Delete(0); } CartView.RowFilter = ""; //tạo dòng mới và thêm vào bảng DataRow dr = Cart.NewRow(); dr[0] = qty; dr[1] = item; dr[2] = price; Bài tập thực hành Chuyên đề Visual Studio .NET 150
  • 151. Cart.Rows.Add(dr); MyDataGrid.EditItemIndex = -1; BindGrid(); } //kết buộc dữ liệu thông qua thuộc tính DataSource của lưới public void BindGrid() { MyDataGrid.DataSource = CartView; MyDataGrid.DataBind(); } </script> <body style="font: 10pt verdana"> <form runat="server"> <h3><font face="Verdana">Using an Edit Command Column in DataGrid</font></h3> //Khai báo các thông số cho lưới, các sự kiện trên lưới: OnEditCommand: khi người dùng nhấn chuỗi hiệu chỉnh (Edit) OnCancelCommand: nhấn chuỗi bỏ qua hiệu chỉnh (Cancel) OnUpdateCommand: nhấn chuỗi cập nhật hiệu chỉnh (Update) <asp:DataGrid id="MyDataGrid" runat="server" BorderColor="black" BorderWidth="1" CellPadding="3" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" OnEditCommand="MyDataGrid_Edit" OnCancelCommand="MyDataGrid_Cancel" OnUpdateCommand="MyDataGrid_Update" AutoGenerateColumns="false" > Bài tập thực hành Chuyên đề Visual Studio .NET 151
  • 152. // các thông số hiệu chỉnh trên cột, ở đây ta chỉ cho người // dùng hiệu chỉnh trên cột số lượng và giá hóa đơn <Columns> <asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update" ItemStyle-Wrap="false" HeaderText="Edit Command Column" HeaderStyle-Wrap="false" /> <asp:BoundColumn HeaderText="Item" ReadOnly="true" DataField="Item"/> <asp:BoundColumn HeaderText="Quantity" DataField="Qty"/> <asp:BoundColumn HeaderText="Price" DataField="Price"/> </Columns> </asp:DataGrid> </form> </body> </html> Điều khiển xác nhận hợp lệ Việc xác nhận hợp lệ là cần thiết với các ứng dụng cần yêu cầu nhập liệu, việc đưa ra các điều khiển có khả năng xác nhận hợp lệ trực tiếp dưới máy khách lẫn ở trên máy chủ, đây có thể là một tính năng mới của ASP.NET, ta không cần phải viết mã kiểm tra gì cả, mã kiểm tra dưới trình duyệt ( chẳng hạn như Java Script ) sẽ được ASP.NET tự động phát sinh. Để gắn một điều khiển bắt lỗi vào một điều khiển cần bắt lỗi ta chỉ cần gán thuộc tính ControlToValidate của điều khiển bắt lỗi bằng giá trị định danh id của điều khiển cần bắt lỗi, ví dụ: Để bắt lỗi điều khiển TextBox không được trống, ta viết má như sau: //điều khiển cần bắt lỗi <ASP:TextBox id=TextBox1 runat=server /> //điều khiển bắt lỗi hộp nhập liệu TextBox1 Bài tập thực hành Chuyên đề Visual Studio .NET 152
  • 153. <asp:RequiredFieldValidator id="RequiredFieldValidator2" ControlToValidate="TextBox1" ErrorMessage="Card Number. " Display="Static" Width="100%" runat=server> * </asp:RequiredFieldValidator> Ví dụ của chúng ta sẽ cho hiển thị 2 hộp thoại DropDownList, 2 nút chọn RadioButton và một hộp thoại nhập TextBox, nếu tồn tại mục nhập nào trống khi nhấn nút xác nhận Validate, thì các điều khiển xác nhận hợp lệ sẽ hiển thị lỗi tương ứng. Thông điệp lỗi có thể được hiển thị theo ba cách khác nhau: liệt kê theo danh sách (List), liệt kê trên cùng một dòng ( Single Paragraph ), liệt kê danh sách với dấu chấm tròn ở đầu ( Bullet List ). Mã hoàn chỉnh của ví dụ được liệt kê như sau: // không cho phép điều khiển xác nhận hợp lệ dưới máy khách bằng // cách gán thuộc tính clienttarget = downlevel <%@ Page clienttarget=downlevel %> <html> <head> <script language="C#" runat=server> // thay đổi chế___ độ hiển thị lỗi bằng cách chọn 1 trong 3 mục // trong hộp thoại ListBox void ListFormat_SelectedIndexChanged(Object Sender, EventArgs E ) { valSum.DisplayMode = (ValidationSummaryDisplayMode) ListFormat.SelectedIndex; } </script> </head> <body> <h3><font face="Verdana">Ví dụ về___ xác nhận điều khiển hợp lệ ValidationSummary</font></h3> <p> Bài tập thực hành Chuyên đề Visual Studio .NET 153
  • 154. <form runat="server"> <table cellpadding=10><tr> <td> <table bgcolor="#eeeeee" cellpadding=10><tr><td colspan=3> <font face=Verdana size=2><b>Credit Card Information</b></font></td></tr> <tr> <td align=right> <font face=Verdana size=2>Card Type:</font></td> <td> // danh sách các nút chọn được bắt lỗi bởi điều //khiển xác nhận hợp lệ RequireFieldValidator1 <ASP:RadioButtonList id=RadioButtonList1 RepeatLayout="Flow" runat=server> <asp:ListItem>MasterCard</asp:ListItem> <asp:ListItem>Visa</asp:ListItem> </ASP:RadioButtonList> </td> //điều khiển xác nhận hợp lệ cho các nút chọn //RadioButtonList1 <td align=middle rowspan=1> <asp:RequiredFieldValidator id="RequiredFieldValidator1" ControlToValidate="RadioButtonList1" ErrorMessage="Card Type. " Display="Static" InitialValue="" Width="100%" runat=server> * </asp:RequiredFieldValidator> </td></tr> <tr> <td align=right> <font face=Verdana size=2>Card Number:</font> Bài tập thực hành Chuyên đề Visual Studio .NET 154
  • 155. </td> <td> <ASP:TextBox id=TextBox1 runat=server /> </td> <td> //điều khiển xác nhận hợp lệ trên hộp thoại //nhập liệu TextBox, nếu chuỗi là trống khi //nhấn nút Validate thì sẽ___ bị bắt lỗi. <asp:RequiredFieldValidator id="RequiredFieldValidator2" ControlToValidate="TextBox1" ErrorMessage="Card Number. " Display="Static" Width="100%" runat=server> * </asp:RequiredFieldValidator> </td> </tr> <tr> <td align=right> <font face=Verdana size=2>Expiration Date:</font> </td> <td> //hộp thoại DropDownList dùng để hiển thị //danh sách các ngày, nếu người dùng chọn //mục trống trong DropDownList này thì sẽ___ bị //điều khiển xác nhận hợp lệ //RequireFieldValidator3 bắt lỗi <ASP:DropDownList id=DropDownList1 runat=server> <asp:ListItem></asp:ListItem> <asp:ListItem >06/00</asp:ListItem> <asp:ListItem >07/00</asp:ListItem> <asp:ListItem >08/00</asp:ListItem> <asp:ListItem >09/00</asp:ListItem> Bài tập thực hành Chuyên đề Visual Studio .NET 155
  • 156. <asp:ListItem >10/00</asp:ListItem> <asp:ListItem >11/00</asp:ListItem> <asp:ListItem >01/01</asp:ListItem> <asp:ListItem >02/01</asp:ListItem> <asp:ListItem >03/01</asp:ListItem> <asp:ListItem >04/01</asp:ListItem> <asp:ListItem >05/01</asp:ListItem> <asp:ListItem >06/01</asp:ListItem> <asp:ListItem >07/01</asp:ListItem> <asp:ListItem >08/01</asp:ListItem> <asp:ListItem >09/01</asp:ListItem> <asp:ListItem >10/01</asp:ListItem> <asp:ListItem >11/01</asp:ListItem> <asp:ListItem >12/01</asp:ListItem> </ASP:DropDownList> </td> <td> //điều khiển xác nhận hợp lệ trên //DropDownList1 hiển thị ngày hết hạn, nếu //người dùng chọn một mục trống trên //DropDownList thì điều khiển này sẽ___ phát //sinh ra lỗi <asp:RequiredFieldValidator id="RequiredFieldValidator3" ControlToValidate="DropDownList1" ErrorMessage="Expiration Date. " Display="Static" InitialValue="" Width="100%" runat=server> * </asp:RequiredFieldValidator></td> </tr> Bài tập thực hành Chuyên đề Visual Studio .NET 156
  • 157. <tr> <td> //nút nhấn để xác định hợp lệ <ASP:Button id=Button1 text="Validate" runat=server /></td></tr> </table> </td> <td valign=top> <table cellpadding=20><tr><td> //điều khiển dùng để hiện thị các lỗi lên trang, //nó sẽ___ bắt bất kỳ lỗi nào được phát sinh bởi các //điều khiển DropDownList để hiển thị <asp:ValidationSummary ID="valSum" runat="server" HeaderText="You must enter a value in the following fields:" Font-Name="verdana" Font-Size="12" /> </td></tr></table> </td> </tr> </table> <font face="verdana" size="-1">Select the type of validation summary display you wish: </font> //Danh sách liệt kê 3 cách hiển thị lỗi <asp:DropDownList id="ListFormat" AutoPostBack=true OnSelectedIndexChanged="ListFormat_SelectedIndexChanged" runat=server > <asp:ListItem>List</asp:ListItem> <asp:ListItem selected>Bulleted List</asp:ListItem> <asp:ListItem>Single Paragraph</asp:ListItem> </asp:DropDownList> </form> Bài tập thực hành Chuyên đề Visual Studio .NET 157
  • 158. </body> </html> 4.5 Các dịch vụ Web Hiện nay, vẫn còn một số hạn chế lớn trong các ứng dụng Web. Người dùng bị giới hạn chỉ thực hiện được những nội dung đã được cấu trúc cho một trang cụ thể và xem dữ liệu thông qua một số giao diện cụ thể nào đó đã được thiết kế trên máy chủ. Do đó người dùng muốn lấy được thông tin được linh động và hiệu quả hơn. Hơn nữa, thay vì ta hiển thị thông tin thông qua trình duyệt Web, ta muốn chạy một phần mềm trực tiếp trên máy khách mà có thể trao đổi dữ liệu trên máy chủ tuỳ ý. Công nghệ .NET cho phép xây dụng cách dịch vụ Web ( Web Services ) đáp ứng được các yêu cầu trên. Ý tưởng chính là: thay vì liệt kê các thông tin theo dạng HTML, trang tạo sẵn một loạt các lệnh gọi hàm. Các lệnh gọi hàm này có thể trao đổi thông tin qua lại giữa các hệ cơ sở dữ liệu trên máy chủ. Các hàm này có thể chấp nhận các tham số và có thể trả về một giá trị tùy ý. Các dịch vụ Web vẫn dựa trên giao thức HTTP để truyền dữ liệu, đồng thời nó cần phải sử dụng thêm một loại giao thức để phục vụ cho việc gọi hàm. Hiện nay có hai giao thức được dùng chủ yếu là: SOAP ( Simple Object Access Protocol ) và SDL ( Service Description Language, đây là giao thức riêng của Microsoft ). Cả hai giao thức này đều được xây dụng dựa trên XML, mục đích chung của chúng là giúp định nghĩa các lệnh gọi hàm, tham số và giá trị. Ngoài ra, Microsoft cũng đưa ra thêm một ý tưởng mới về tập tin Discovery File, có phần mở rộng là .disco. Tập tin dạng này dùng để cung cấp các thông tin cho các trình duyệt để các trình duyệt này có thể xác định được các trang trên các máy chủ mà có chứa các dịch vụ Web. Sau đây, ta sẽ tìm hiểu một ví dụ nhằm minh họa việc tạo ra một dịch vụ Web, đóng vai trò là một thư viện chứa một tập các hàm tiện ích. Trang Web của chúng ta sẽ sử dụng các hàm của dịch vụ này. Dịch vụ Web của chúng sẽ có tên MathService, đơn giản là định nghĩa bốn phương thức cộng, trừ, nhân, chia trên hai số thực bất kỳ. Mỗi phương thức đều nhận vào hai đối số kiểu số thực và trả về kết quả cũng có kiểu số thực. Đầu tiên ta cần tạo một dự án kiểu Web Service bằng cách chọn: New ProjectVisual C# ProjectASP.NET Web Service và đặt tên cho dự án là MathService và đổi tên dịch vụ thành MathService.asmx. NET có tạo sẵn cho chúng ta một số tập tin như: • Service1.asmx: được trình duyệt yêu cầu, tương tự với tập tin .aspx. • WebService1.cs: trang chứa mã C# quản lý. • DiscoFile1.disco: tập tin khám phá. Bài tập thực hành Chuyên đề Visual Studio .NET 158
  • 159. Trong ví dụ này, chúng ta sẽ tạo ra một Web Form mới và thiết giao diện như sau: Web Form sẽ gọi thực thi các hàm của dịch vụ Web. Dự án của ta sẽ thừa kế namespace là System.Web.Services.WebService,nơi chứa các thuộc tính và phương thức cần thiết để tạo dịch vụ Web. public class MathService: System.Web.Services.WebService Trên mỗi phương thức ta cần khai báo thuộc tính [WebMethod], để chỉ ra đây là phương thức sẽ được sử dụng cho dịch vụ Web. Mã của tập tin dịch vụ sẽ như sau: using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace MathService { public class MathService:System.Web.Services.WebService { public MathService() { InitializeComponent(); } #region Component Designer generated code private IContainer components = null; private void InitializeComponent() { } protected override void Dispose( bool disposing ) { if(disposing && components != null) { Bài tập thực hành Chuyên đề Visual Studio .NET 159
  • 160. components.Dispose(); } base.Dispose(disposing); } #endregion //4 hàm toán học của dịch vụ Web, trên mỗi phương thức //ta cần khai báo thuộc tính [WebMethod] để chỉ đây là //phương thức dành cho dịch vụ Web. [WebMethod] public float Add(float a, float b) { return a + b; } [WebMethod] public float Subtract(float a, float b) { return a - b; } [WebMethod] public float Multiply(float a, float b) { return a * b; } [WebMethod] public float Divide(float a, float b) { if (b==0) return -1; return a / b; } } Bài tập thực hành Chuyên đề Visual Studio .NET 160
  • 161. } Bây giờ chúng ta sẽ viết mã thực thi cho trang Web. Trang Web của chúng ta sẽ gọi các hàm của dịch vụ tương ứng với các phép cộng, trừ, nhân, chia . Sau đây là mã của trang Web: <%@ Import Namespace="MathService" %> <html> <script language="C#" runat="server"> float operand1 = 0; float operand2 = 0; public void Submit_Click(Object sender, EventArgs E) { try { operand1 = float.Parse(Operand1.Text); operand2 = float.Parse(Operand2.Text); } catch (Exception) { /* bỏ qua lỗi nếu có */ } Các dịch vụ Web Gvhd: Nguyễn Tấn Trần Minh Khang 195 //tạo ra một đối tượng dịch vụ MathService để có thể truy cập đến //các hàm thành viên của chúng. MathService service = new MathService(); switch (((Control)sender).ID) { case "Add": Result.Text = "<b>Result</b> = " + service.Add(operand1, operand2).ToString(); break; case "Subtract": Result.Text = "<b>Result</b> = " + service.Subtract(operand1, operand2).ToString(); break; case "Multiply": Result.Text = "<b>Result</b> = " + service.Multiply(operand1, operand2).ToString(); break; case "Divide": Result.Text = "<b>Result</b> = " + Bài tập thực hành Chuyên đề Visual Studio .NET 161
  • 162. service.Divide(operand1, operand2).ToString(); break; } } </script> <body style="font: 10pt verdana"> <h4>Using a Simple Math Service </h4> <form runat="server"> <div style="padding:15,15,15,15;backgroundcolor: beige;width:300;border-color:black;borderwidth: 1;border-style:solid"> Operand 1:<br> <asp:TextBox id="Operand1" Text="15" runat="server" /><br> Operand 2:<br> <asp:TextBox id="Operand2" Text="5" runat="server" /><p> <input type="submit" id="Add" value="Add" OnServerClick="Submit_Click" runat="server"> <input type="submit" id="Subtract" value="Subtract" OnServerClick="Submit_Click" runat="server"> <input type="submit" id="Multiply" value="Multiply" OnServerClick="Submit_Click" runat="server"> <input type="submit" id="Divide" value="Divide" OnServerClick="Submit_Click" runat="server"> <p> <asp:Label id="Result" runat="server" /> </div> </form> </body> Bài tập thực hành Chuyên đề Visual Studio .NET 162
  • 163. </html> PHẦN 5 PHỤ LỤC Phụ lục A Chuỗi kết nối cho các loại nguồn dữ liệu Phụ lục B Bảng tương quan/chuyển đổi kiểu dữ liệu ở .NET Framework với các Data Provider .NET Framework System.Data.DbType SqlDbType OleDbType OdbcType type bool Boolean Bit Boolean Bit byte Byte TinyInt UnsignedTinyInt TinyInt byte[] Binary VarBinary. Việc VarBinary Binary chuyển đổi ngầm định này là không đúng nếu mảng byte là lớn hơn kích thước tối đa của một VarBinary (8000 bytes). char Không hỗ trợ. Char Char DateTime DateTime DateTime DBTimeStamp DateTime Decimal Decimal Decimal Decimal Numeric double Double Float Double Double float Single Real Single Real Guid Guid UniqueIdentifier Guid UniqueIdentifi Int16 Int16 SmallInt SmallInt SmallInt Int32 Int32 Int Int Int Int64 Int64 BigInt BigInt BigInt object Object Variant Variant Không hỗ trợ. string String NVarChar. VarWChar NVarChar Chuyển đổi ngầm định này là không đúng nếu string lớn hơn kích Bài tập thực hành Chuyên đề Visual Studio .NET 163
  • 164. .NET Framework System.Data.DbType SqlDbType OleDbType OdbcType type thước tối đa của một NVarChar (4000 ký tự). TimeSpan Time Không hỗ trợ. DBTime Time UInt16 UInt16 Không hỗ trợ. UnsignedSmallInt Int UInt32 UInt32 Không hỗ trợ. UnsignedInt BigInt UInt64 UInt64 Không hỗ trợ. UnsignedBigInt Numeric AnsiString VarChar VarChar VarChar AnsiStringFixedLength Char Char Char Currency Money Currency Không hỗ trợ. Date Không hỗ trợ. DBDate Date SByte Không hỗ trợ. TinyInt Không hỗ trợ. StringFixedLength NChar WChar NChar Time Không hỗ trợ. DBTime Time VarNumeric Không hỗ trợ. VarNumeric Không hỗ trợ. Bài tập thực hành Chuyên đề Visual Studio .NET 164
  • 165. PHẦN TÀI LIỆU THAM KHẢO (1) Stephen C. Perry, Core C# and .NET, Prentice Hall PTR, 2005 (2) Microsoft Corporation, MSDN 2005 Bài tập thực hành Chuyên đề Visual Studio .NET 165