BÀI 6: TRUY XUẤT DỮ LIỆU VỚI ADO.NET

6.1. Giới thiệu lập trình cơ sở dữ liệu (ADO.NET)

  • Khái niệm: ADO.NET là một bộ công nghệ (các lớp) trong .NET Framework, cung cấp khả năng tương tác (kết nối, truy vấn, cập nhật) với các nguồn dữ liệu, phổ biến nhất là các Hệ quản trị CSDL (như SQL Server, Access, Oracle…).

  • Mô hình kết nối (Connected Model): Là mô hình làm việc chính của bài này.

    • Nguyên tắc: Mở kết nối -> Gửi lệnh -> Nhận kết quả -> Đóng kết nối.

    • Các đối tượng chính: Connection, Command, DataReader.

  • Chuỗi kết nối (Connection String): Là một chuỗi văn bản chứa “chìa khóa” để kết nối đến CSDL, bao gồm: tên máy chủ (Server), tên CSDL (Database), thông tin xác thực (Username/Password hoặc Tích hợp Windows).

    • Ví dụ (SQL Server với xác thực Windows): "Server=MAYTINH\\SQLEXPRESS; Database=QuanLySinhVien; Integrated Security=True;"

    • Ví dụ (SQL Server với xác thực SQL): "Server=.; Database=QuanLySinhVien; User Id=sa; Password=123;"

6.2. Đối tượng SQLConnection (Kết nối)

  • (Chúng ta tập trung vào SqlClient cho SQL Server, OleDb dùng cho các CSDL khác như Access).

  • Không gian tên: System.Data.SqlClient

  • Công dụng: Đại diện cho một phiên kết nối duy nhất đến CSDL SQL Server.

  • Phương thức chính:

    • Open(): Mở kết nối.

    • Close(): Đóng kết nối (Rất quan trọng).

  • Cú pháp:

    C#
    using System.Data.SqlClient;
    // ...
    string chuoiKetNoi = "..."; // Chuỗi kết nối của bạn
    SqlConnection conn = new SqlConnection(chuoiKetNoi);
    
    try
    {
        conn.Open();
        // ... Làm việc với CSDL ở đây ...
        MessageBox.Show("Kết nối thành công!");
    }
    catch (Exception ex)
    {
        MessageBox.Show("Lỗi kết nối: " + ex.Message);
    }
    finally
    {
        // Đảm bảo kết nối luôn được đóng
        if (conn.State == System.Data.ConnectionState.Open)
        {
            conn.Close();
        }
    }
    

6.3. Đối tượng SQLCommand (Lệnh)

  • Công dụng: Đại diện cho một câu lệnh SQL (SELECT, INSERT, UPDATE, DELETE) hoặc một Stored Procedure cần thực thi tại CSDL.

  • Thuộc tính chính:

    • Connection: Chỉ định kết nối SqlConnection mà lệnh này sẽ dùng.

    • CommandText: Nội dung câu lệnh SQL (ví dụ: "SELECT * FROM SinhVien").

  • Các phương thức thực thi (Execute):

    1. ExecuteNonQuery(): Dùng cho các lệnh không trả về dữ liệu (INSERT, UPDATE, DELETE). Nó trả về số dòng bị ảnh hưởng.

    2. ExecuteScalar(): Dùng khi câu lệnh chỉ trả về một giá trị duy nhất (ví dụ: SELECT COUNT(*) FROM SinhVien).

    3. ExecuteReader(): Dùng cho lệnh SELECT trả về nhiều dòng, nhiều cột. Nó trả về một đối tượng SqlDataReader.

6.4. Đối tượng SQLDataReader (Đọc dữ liệu)

  • Công dụng: Cung cấp một cách đọc dữ liệu chỉ tiến (forward-only), hiệu suất cao từ kết quả của lệnh SELECT.

  • Nguyên tắc: Đọc từng dòng (record) một.

  • Phương thức chính:

    • Read(): Di chuyển đến dòng tiếp theo. Trả về true nếu còn dòng để đọc, false nếu đã hết.

  • Cách lấy dữ liệu: Dùng ten_reader["TenCot"] hoặc ten_reader[chi_so_cot].

  • Ví dụ (SELECT):

    C#
    // (Giả sử conn đã được mở)
    SqlCommand cmd = new SqlCommand("SELECT MaSV, HoTen FROM SinhVien", conn);
    
    SqlDataReader reader = cmd.ExecuteReader();
    
    // Dùng 'while' để đọc từng dòng
    while (reader.Read())
    {
        // Lấy dữ liệu từ dòng hiện tại
        string maSV = reader["MaSV"].ToString();
        string hoTen = reader["HoTen"].ToString();
    
        // (Hiển thị lên ListBox hoặc ComboBox...)
        listBox1.Items.Add(maSV + " - " + hoTen);
    }
    
    // Phải đóng Reader sau khi dùng xong
    reader.Close();
    // (Sau đó đóng conn)
    

 

6.5. Đối tượng SQLParameter (Tham số)

  • Vấn đề (SQL Injection): Nếu bạn ghép chuỗi trực tiếp từ TextBox vào câu SQL, hacker có thể nhập code độc hại.

    • Code nguy hiểm: string sql = "SELECT * FROM Users WHERE User='" + txtUser.Text + "';"

    • Hacker nhập: ' OR 1=1 --

    • Câu lệnh trở thành: SELECT * FROM Users WHERE User='' OR 1=1 --'; (Luôn đúng, bypass login).

  • Giải pháp (SqlParameter): Dùng tham số (dấu @) để truyền giá trị vào CSDL một cách an toàn. ADO.NET sẽ tự xử lý các ký tự đặc biệt.

  • Ví dụ (INSERT an toàn):

    C#
     
    // (Giả sử conn đã mở, có txtMaSV và txtHoTen)
    string sql = "INSERT INTO SinhVien (MaSV, HoTen) VALUES (@Ma, @Ten)";
    SqlCommand cmd = new SqlCommand(sql, conn);
    
    // Thêm tham số
    cmd.Parameters.AddWithValue("@Ma", txtMaSV.Text);
    cmd.Parameters.AddWithValue("@Ten", txtHoTen.Text);
    
    // Thực thi (dùng ExecuteNonQuery)
    int soDongAnhHuong = cmd.ExecuteNonQuery();
    
    if (soDongAnhHuong > 0)
    {
        MessageBox.Show("Thêm thành công!");
    }
    // (Đóng conn)

6.6. Câu hỏi ôn tập

  1. ADO.NET là gì?

  2. Chuỗi kết nối (Connection String) dùng để làm gì? Nêu 3 thông tin quan trọng trong đó.

  3. Trình bày 3 phương thức Execute của SqlCommand và công dụng của chúng?

  4. SqlDataReader dùng để làm gì? Tại sao phải đóng nó sau khi dùng?

  5. Tại sao phải dùng SqlParameter thay vì cộng chuỗi trực tiếp vào câu lệnh SQL?

6.7. Bài tập thực hành :

Yêu cầu chuẩn bị:

  • Cài đặt SQL Server (Express).

  • Tạo một CSDL tên QuanLySinhVien.

  • Tạo một bảng tên SinhVien có 3 cột: MaSV (varchar(10), Primary Key), HoTen (nvarchar(100)), NgaySinh (date).

Bài tập 6.1: Tạo Form Quản lý Sinh viên

  1. Tạo dự án Windows Forms Application tên BaiTap6_QLSV.

  2. Thiết kế giao diện gồm:

    • 3 Label (MaSV, HoTen, NgaySinh).

    • 3 TextBox (tên txtMaSV, txtHoTen, txtNgaySinh).

    • 4 Button (tên btnThem, btnSua, btnXoa, btnTimKiem).

    • 1 ListBox (tên lstSinhVien) để hiển thị danh sách.

Hướng dẫn làm bài

Đây là bài mở đầu cho Bài 6: Truy xuất dữ liệu với ADO.NET. Trước khi bắt đầu tạo Form, chúng ta cần một bước chuẩn bị “ngầm” rất quan trọng là tạo Cơ sở dữ liệu (CSDL) để sau này Form có nơi để lưu dữ liệu.

🛠️ Bước 0: Chuẩn bị Cơ sở dữ liệu (SQL Server)

Vì bài này về quản lý sinh viên, bạn cần tạo bảng trong SQL Server trước.

  1. Mở SQL Server Management Studio.

  2. Tạo một CSDL mới tên là QuanLySinhVien.

Tạo một bảng tên SinhVien bằng câu lệnh SQL sau (hoặc tạo bằng giao diện):

SQL

 
CREATE TABLE SinhVien (
    MaSV VARCHAR(20) PRIMARY KEY,
    HoTen NVARCHAR(100),
    NgaySinh DATE
);
Nhập sẵn 1-2 dòng dữ liệu mẫu để lát nữa hiển thị lên ListBox cho dễ kiểm tra.
🖥️ Bước 1: Tạo dự án Windows Forms
  1. Mở Visual Studio 2008

  2. Chọn File -> New -> Project….

  3. Chọn Visual C# -> Windows Forms Application.

  4. Đặt tên (Name) là: BaiTap6_QLSV.

  5. Nhấn OK.

🎨 Bước 2: Thiết kế giao diện (Kéo – Thả)

Chúng ta sẽ sắp xếp các công cụ (Controls) từ Toolboxvào Form.

Gợi ý bố cục:

  • Phần nhập liệu (Bên trái): Các Label và TextBox xếp thẳng hàng dọc.

  • Phần nút bấm (Bên dưới phần nhập liệu): Các Button xếp ngang hàng.

  • Phần hiển thị (Bên phải hoặc dưới cùng): ListBox to để chứa danh sách.

Thao tác:

  1. 3 Label: Kéo 3 Label từ Toolbox thả vào Form.

  2. 3 TextBox: Kéo 3 TextBox thả vào bên cạnh các Label tương ứng.

  3. 4 Button: Kéo 4 Button thả vào Form.

  4. 1 ListBox: Kéo 1 ListBox thả vào Form (kéo dãn nó to ra để chứa được nhiều dòng).

⚙️ Bước 3: Thiết lập thuộc tính (Properties)

Đây là bước quan trọng nhất để khớp với yêu cầu của đề bài. Bạn cần đặt tên (Name) chính xác để viết code ở các bài sau.

Chọn từng đối tượng trên Form và sửa trong cửa sổ Properties theo bảng sau:

A. Nhóm Nhập liệu (Label & TextBox)

Đối tượngThuộc tính (Property)Giá trị (Value)Ghi chú
Label 1TextMã Sinh viên:Nhãn hiển thị
TextBox 1(Name)txtMaSVTên dùng trong code
Label 2TextHọ và Tên: 
TextBox 2(Name)txtHoTen 
Label 3TextNgày sinh: 
TextBox 3(Name)txtNgaySinhLưu ý: Nhập dạng ngày tháng

B. Nhóm Nút bấm (Button)

Đối tượngThuộc tính (Property)Giá trị (Value)Ghi chú
Button 1(Name)btnThemNút Thêm
 TextThêm 
Button 2(Name)btnSuaNút Sửa
 TextSửa 
Button 3(Name)btnXoaNút Xóa
 TextXóa 
Button 4(Name)btnTimKiemNút Tìm kiếm
 TextTìm kiếm 

C. Nhóm Hiển thị (ListBox) & Form

Đối tượngThuộc tính (Property)Giá trị (Value)
ListBox 1(Name)lstSinhVien
Form1TextQuản lý Sinh viên
 StartPositionCenterScreen (Để Form hiện giữa màn hình)

🏁 Bước 4: Chạy thử giao diện

  1. Nhấn F5 để chạy chương trình.

  2. Lúc này, bạn sẽ thấy giao diện đã hoàn thành. Bạn có thể gõ chữ vào các ô, bấm nút (nhưng chưa có gì xảy ra vì chưa viết code).

Bài tập 6.2: Viết code cho các chức năng (CRUD)

  1. Tạo Lớp kết nối (Nâng cao): Tạo một lớp tĩnh (ví dụ: Database.cs) để quản lý chuỗi kết nối và các hàm Open/Close Connection chung.

  2. Code Nút “Thêm” (btnThem_Click):

    • Viết câu lệnh INSERT sử dụng SqlParameter (như ví dụ 6.5).

    • Sử dụng ExecuteNonQuery().

    • Sau khi thêm, gọi lại hàm tải dữ liệu lên ListBox.

  3. Code Nút “Xóa” (btnXoa_Click):

    • Lấy MaSV từ txtMaSV.

    • Viết câu lệnh DELETE FROM SinhVien WHERE MaSV = @Ma.

    • Dùng SqlParameter để thêm @Ma.

    • Sử dụng ExecuteNonQuery().

  4. Code Nút “Sửa” (btnSua_Click):

    • Viết câu lệnh UPDATE SinhVien SET HoTen = @Ten, NgaySinh = @Ngay WHERE MaSV = @Ma.

    • Dùng SqlParameter cho cả 3 tham số.

    • Sử dụng ExecuteNonQuery().

  5. Tải dữ liệu lên ListBox (Hàm LoadData):

    • Viết một hàm riêng: void LoadData()

    • Bên trong hàm:

      • Mở kết nối.

      • Dùng SqlCommand với câu lệnh "SELECT * FROM SinhVien".

      • Dùng SqlDataReader (như ví dụ 6.4).

      • Trong vòng lặp while (reader.Read()), thêm dữ liệu vào lstSinhVien (ví dụ: lstSinhVien.Items.Add(reader["MaSV"] + " | " + reader["HoTen"]);).

      • Nhớ lstSinhVien.Items.Clear() trước vòng lặp.

      • Đóng ReaderConnection.

    • Gọi hàm LoadData() này trong sự kiện Form_Load và sau mỗi lần Thêm, Sửa, Xóa.

  6. (Nâng cao) Click vào ListBox: Viết sự kiện lstSinhVien_SelectedIndexChanged để khi người dùng chọn 1 dòng, thông tin sinh viên đó được hiển thị ngược lại lên các TextBox.

Hướng dẫn làm bài:

🔗 Bước 1: Khai báo thư viện và Chuỗi kết nối

Để làm việc với SQL Server, việc đầu tiên là phải “gọi” thư viện System.Data.SqlClient.

  1. Nhấn F7 (hoặc chuột phải vào Form -> View Code) để mở màn hình viết code (Form1.cs).

  2. Kéo lên trên cùng, thêm dòng này vào danh sách using:

    C#
     
    using System.Data.SqlClient; // Thư viện để làm việc với SQL Server
    
  3. Bên trong class Form1, khai báo biến kết nối toàn cục:

    C#
     
    public partial class Form1 : Form
    {
        // 1. Tạo chuỗi kết nối
        // Lưu ý: Thay "TÊN_MÁY_CỦA_BẠN\\SQLEXPRESS" bằng tên server thực tế của bạn
        // Nếu dùng SQL xác thực bằng quyền Windows: Integrated Security=True
        string strKetNoi = @"Data Source=.\SQLEXPRESS;Initial Catalog=QuanLySinhVien;Integrated Security=True";
    
        // 2. Khai báo đối tượng kết nối
        SqlConnection conn = null;
    
        public Form1()
        {
            InitializeComponent();
        }
    // ...
    

🔄 Bước 2: Viết hàm LoadData() (Tải dữ liệu lên ListBox)

Chúng ta cần một hàm dùng chung để tải lại danh sách sinh viên lên ListBox mỗi khi mở Form hoặc sau khi Thêm/Sửa/Xóa xong. Hàm này sử dụng SqlDataReader.

Viết hàm này bên dưới hàm Form1 (Constructor):

C#
 
private void LoadData()
{
    try
    {
        // Khởi tạo kết nối
        conn = new SqlConnection(strKetNoi);
        conn.Open(); // Mở kết nối

        // Tạo câu lệnh truy vấn
        string sql = "SELECT * FROM SinhVien";
        SqlCommand cmd = new SqlCommand(sql, conn);

        // Thực thi và đọc dữ liệu (Dùng SqlDataReader)
        SqlDataReader reader = cmd.ExecuteReader();

        // Xóa danh sách cũ trong ListBox trước khi nạp mới
        lstSinhVien.Items.Clear();

        // Đọc từng dòng
        while (reader.Read())
        {
            string ma = reader["MaSV"].ToString();
            string ten = reader["HoTen"].ToString();
            
            // Hiển thị dạng: "SV01 - Nguyễn Văn A"
            lstSinhVien.Items.Add(ma + " - " + ten);
        }

        reader.Close(); // Đóng đầu đọc
    }
    catch (Exception ex)
    {
        MessageBox.Show("Lỗi tải dữ liệu: " + ex.Message);
    }
    finally
    {
        // Luôn đóng kết nối
        if (conn != null) conn.Close();
    }
}

Thêm sự kiện Form_Load:

  1. Quay lại giao diện thiết kế (Design).

  2. Nhấp đúp vào nền của Form (chỗ trống bất kỳ).

  3. Trong hàm Form1_Load, gọi hàm LoadData():

    C#
     
    private void Form1_Load(object sender, EventArgs e)
    {
        LoadData(); // Tải dữ liệu ngay khi mở phần mềm
    }
    

➕ Bước 3: Viết code nút THÊM (btnThem)

Nút này sử dụng câu lệnh INSERT. Chúng ta phải dùng SqlParameter để tránh lỗi nếu tên có dấu nháy đơn và bảo mật (chống SQL Injection).

  1. Nhấp đúp vào nút Thêm.

  2. Viết code:

C#
 
private void btnThem_Click(object sender, EventArgs e)
{
    try
    {
        conn = new SqlConnection(strKetNoi);
        conn.Open();

        // Câu lệnh INSERT với tham số (@Ma, @Ten, @Ngay)
        string sql = "INSERT INTO SinhVien (MaSV, HoTen, NgaySinh) VALUES (@Ma, @Ten, @Ngay)";
        SqlCommand cmd = new SqlCommand(sql, conn);

        // Gán giá trị từ TextBox vào tham số
        cmd.Parameters.AddWithValue("@Ma", txtMaSV.Text);
        cmd.Parameters.AddWithValue("@Ten", txtHoTen.Text);
        // Lưu ý: txtNgaySinh phải nhập đúng định dạng (VD: 2000-01-01)
        cmd.Parameters.AddWithValue("@Ngay", txtNgaySinh.Text); 

        // Thực thi lệnh (ExecuteNonQuery dùng cho Insert/Update/Delete)
        int ketQua = cmd.ExecuteNonQuery();

        if (ketQua > 0)
        {
            MessageBox.Show("Thêm thành công!");
            LoadData(); // Tải lại danh sách để thấy sinh viên mới
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("Lỗi thêm: " + ex.Message);
    }
    finally
    {
        if (conn != null) conn.Close();
    }
}

❌ Bước 4: Viết code nút XÓA (btnXoa)

Nút này dùng lệnh DELETE. Chúng ta xóa dựa trên MaSV (Khóa chính).

  1. Nhấp đúp vào nút Xóa.

  2. Viết code:

C#
 
private void btnXoa_Click(object sender, EventArgs e)
{
    // Hỏi người dùng có chắc chắn xóa không
    if (MessageBox.Show("Bạn có chắc muốn xóa?", "Cảnh báo", MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        try
        {
            conn = new SqlConnection(strKetNoi);
            conn.Open();

            string sql = "DELETE FROM SinhVien WHERE MaSV = @Ma";
            SqlCommand cmd = new SqlCommand(sql, conn);

            // Chỉ cần truyền Mã SV để xóa
            cmd.Parameters.AddWithValue("@Ma", txtMaSV.Text);

            int ketQua = cmd.ExecuteNonQuery();

            if (ketQua > 0)
            {
                MessageBox.Show("Xóa thành công!");
                LoadData(); 
                // Xóa trắng các ô nhập liệu
                txtMaSV.Text = ""; txtHoTen.Text = ""; txtNgaySinh.Text = "";
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("Lỗi xóa: " + ex.Message);
        }
        finally
        {
            if (conn != null) conn.Close();
        }
    }
}

✏️ Bước 5: Viết code nút SỬA (btnSua)

Nút này dùng lệnh UPDATE. Nó cập nhật Tên và Ngày sinh dựa trên Mã SV.

  1. Nhấp đúp vào nút Sửa.

  2. Viết code:

C#
 
private void btnSua_Click(object sender, EventArgs e)
{
    try
    {
        conn = new SqlConnection(strKetNoi);
        conn.Open();

        string sql = "UPDATE SinhVien SET HoTen = @Ten, NgaySinh = @Ngay WHERE MaSV = @Ma";
        SqlCommand cmd = new SqlCommand(sql, conn);

        // Truyền đủ 3 tham số
        cmd.Parameters.AddWithValue("@Ma", txtMaSV.Text);
        cmd.Parameters.AddWithValue("@Ten", txtHoTen.Text);
        cmd.Parameters.AddWithValue("@Ngay", txtNgaySinh.Text);

        int ketQua = cmd.ExecuteNonQuery();

        if (ketQua > 0)
        {
            MessageBox.Show("Sửa thành công!");
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("Lỗi sửa: " + ex.Message);
    }
    finally
    {
        if (conn != null) conn.Close();
    }
}

🧪 Bước 6: Chạy và Kiểm thử

  1. Nhấn F5 để chạy chương trình.

  2. Thử Thêm: Nhập Mã “SV001”, Tên “Nguyễn Văn A”, Ngày “2000-01-01”. Nhấn Thêm. Xem ListBox có hiện lên không.

  3. Thử Sửa: Nhập lại Mã “SV001”, đổi tên thành “Nguyễn Văn B”. Nhấn Sửa.

  4. Thử Xóa: Nhập Mã “SV001”. Nhấn Xóa.