[ADO.NET Tutorial] Lesson 04: Đọc dữ liệu với SqlDataReader

Lesson này hướng dẫn các dùng SqlDataReader để đọc dữ liệu, qua đó bạn sẽ biết được cách dùng SqlDataReader cho những mục đích phù hợp.

Giới thiệu

Một SqlDataReader là đối tượng phù hợp để đọc dữ liệu một cách hiệu quả nhất. Như tên gọi, bạn không thể dùng nó để ghi dữ liệu. SqlDataReader thường được mô tả là luồng dữ liệu fast-forward firehose-like (fire-hose: ống vòi rồng).

Bạn có thể đọc dữ liệu từ đối tượng SqlDataReader theo hướng forward-only trong một thứ tự nhất định. Mỗi lần đọc một vài dữ liệu, bạn phải lưu nó nếu cần thiết bởi vì bạn không thể quay trở lại và đọc nó một lần nữa.

Kiểu thiết kế forward-only của SqlDataReader để giúp nó hoạt động nhanh. Nó không thể di chuyển trực tiếp đến các dòng dữ liệu ở vị trí bất kì và không thể ghi vào dữ liệu nguồn. Do đó, nếu bạn chỉ yêu cầu đọc một nhóm dữ liệu một lần và cần phương pháp nhanh nhất, SqlDataReader là lựa chọn tốt nhất.

Note: Tôi dùng từ “một lần” trong đoạn trước khi nói về lý do tại sao bạn nên dùng một SqlDataReader. Tuy nhiên có những ngoại lệ và trường hợp, sẽ hiệu quả hơn nếu bạn dùng một DataSet để lưu dữ liệu tạm. Vấn đề này nằm ngoài phạm vi của bài này, chúng ta sẽ thảo luận về đối tượng DataSet trong bài tiếp theo.


Tạo một đối tượng SqlDataReader

Có một chút khác biệt để lấy được một thể hiện của SqlDataReader so với các đối tượng ADO.NET khác. Bạn phải gọi phương thức ExecuteReader() của một đối tượng SqlCommand, như:

SqlDataReader rdr = cmd.ExecuteReader();

Phương thức ExecuteReader() của đối tượng SqlCommand, cmd, trả về một thể hiện của SqlDataReader. Tạo một SqlDataReader với toán tử new không có tác dụng gì cho bạn cả. Như bạn đã học trong bài trước, đối tượng SqlCommand chứa tham chiếu đến connection và câu lệnh SQL cần thiết để SqlDataReader lấy được dữ liệu.

Đọc dữ liệu

Bài trước chứa đoạn mã sử dụng SqlDataReader, nhưng chưa được thảo luận tới vì chúng ta sẽ tập trung vào một chủ đề riêng. Bài này được tạo ra từ những gì bạn đã xem và giải thích cách sử dụng SqlDataReader.

Như đã giải thích tước, SqlDataReader trả về dữ liệu qua một luồng liên tục. Để đọc dữ  liệu, bạn phải lấy dữ liệu từ một bảng từng dòng một (row-by-row). Mỗi lần một dòng được đọc, dòng trước đó sẽ không còn hiệu lực. Để đọc lại dòng đó, bạn cần phải tạo một thể hiện mới của SqlDataReader và đọc xuyên qua luồng dữ liệu một lần nữa.

Phương pháp điển hình để đọc từ luồng dữ liệu trả về bởi SqlDataReader là lặp qua mỗi dòng với một vòng lặp while. Đoạn code sau cho thấy cách làm điều này:

while (rdr.Read())

{

    // get the results of each column

    string contact = (string)rdr["ContactName"];

    string company = (string)rdr["CompanyName"];

    string city    = (string)rdr["City"];

    // print out the results

    Console.Write("{0,-25}", contact);

    Console.Write("{0,-20}", city);

    Console.Write("{0,-25}", company);

    Console.WriteLine();

}

Lưu ý phương thức Read() của SqlDataReader, rdr, trong điều kiện của vòng lặp while trong đoạn code trên. Giá trị trả về của Read() là kiểu bool và trả về true đến khi nào vẫn còn dòng để đọc. Sau khi dòng cuối được đọc trong luồng dữ liệu, Read() trả về false.

Trong bài trước, chúng ta lấy cột đầu tiên của dòng bằng cách dùng indexer của SqlDataReader, ví dụ rdr[0]. Bạn có thể lấy mỗi cột của dòng với indexer kiểu số, nhưng cách này rất khó đọc. Ví dụ trên dùng indexer kiểu string, với chuỗi sử dụng là tên cột từ câu truy vân SQL (tên này sẽ trùng với tên cột của bảng nếu bạn dùng dấu sao * trong lấy dữ liệu). Indexer kiểu string sẽ dễ đọc hơn và giúp mã nguồn dễ bảo trì.

Bỏ qua kiểu tham số của indexer, một indexer của SqlDataReader sẽ trả về kiểu object. Đó là lý do tại sao ví dụ trên phải chuyển kết quả về kiểu string. Mỗi lần giá trị được lấy ra, bạn có thể làm bất kì điều gì bạn muốn với chúng, như in ra màn hình với các phương thức của lớp Console.

Bổ sung: Tuy nhiên thay vì sử dụng indexer của SqlDataReader, bạn có thể dùng các phương thức GetXXX() với XXX là kiểu dữ liệu bạn muốn lấy. Tùy vào tên phương thức mà kiểu trả về sẽ tương ứng với nó. Ví dụ cột đầu tiên của bảng là string, bạn có thể gọi như sau:

string value = rdr.GetString(0);

Tuy nhiên như bạn thấy, tham  số của nó là kiểu int, một số chỉ vị trí của cột tính từ 0.

Bạn có thể giải quyết điều này bằng cách dùng phương thức GetOrdinal(string name) với tham số là tên cột và giá trị trả về là vị trí của cột. Như vậy bạn có viết một phương thức mới (trong trường hợp bạn muốn một tạo sub class hay wrapper của SqlDataReader) chấp nhận tham số là tên cột thay vì vị trí, một minh họa đơn giản:

string GetString(string columnName)

{

    int columnIndex = rdr.GetOrdinal(columnName);

    string value =rdr.GetString(columnIndex);

    return value;

}

Hoàn tất

 Luôn nhớ việc đóng SqlDataReader, giống như bạn cần phải đóng SqlConnection. Cho đoạn mã truy xuất dữ liệu trong một khối try và đặt lệnh đóng trong khối finally, giống như sau:

try

{

    // data access code

}

finally

{

    // 3. close the reader

    if (rdr != null)

    {

        rdr.Close();

    }

    // close the connection too

}

Đoạn mã trên kiểm tra SqlDataReader để đảm bảo rằng nó khác null. Sau khi đã xác nhận đó là một thể hiện hợp lệ của SqlDataReader, nó sẽ được đóng lại. Listing 1 cho thấy đoạn mã hoàn chỉnh mà bạn đã thấy một vài phần của nó ở trên:

Listing 1: Using the SqlDataReader

using System;

using System.Data;

using System.Data.SqlClient;

namespace Lesson04

{

    class ReaderDemo

    {

        static void Main()

        {

            ReaderDemo rd = new ReaderDemo();

            rd.SimpleRead();

        }

        public void SimpleRead()

        {

            // declare the SqlDataReader, which is used in

            // both the try block and the finally block

            SqlDataReader rdr = null;

            // create a connection object

            SqlConnection conn = new SqlConnection("Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI");

            // create a command object

            SqlCommand cmd  = new SqlCommand(

                "select * from Customers", conn);

            try

            {

                // open the connection

                conn.Open();

                // 1.  get an instance of the SqlDataReader

                rdr = cmd.ExecuteReader();

                // print a set of column headers

                Console.WriteLine("Contact Name             City                Company Name");

                Console.WriteLine("------------             ------------        ------------");

                // 2.  print necessary columns of each record

                while (rdr.Read())

                {

                    // get the results of each column

                    string contact = (string)rdr["ContactName"];

                    string company = (string)rdr["CompanyName"];

                    string city    = (string)rdr["City"];

                    // print out the results

                    Console.Write("{0,-25}", contact);

                    Console.Write("{0,-20}", city);

                    Console.Write("{0,-25}", company);

                    Console.WriteLine();

               }

            }

            finally

            {

                // 3. close the reader

                if (rdr != null)

                {

                    rdr.Close();

                }

                // close the connection

                if (conn != null)

                {

                    conn.Close();

                }

            }

        }

    }

}

Tổng kết

Đối tượng SqlDataReader cho phép bạn đọc nhanh dữ liệu theo cách forward-only. Bạn lấy dữ liệu bằng cách đọc từng dòng từ luồng dữ liệu. Gọi phương thức Close() của SqlDataReader để đảm bảo sẽ không có tài nguyên nào thất thoát.

Bài tiếp theo trong series này là Lesson 05: Làm việc với Disconnected Data – DataSet và SqlDataAdapter.

Tags: