Cách Kết Nối Cơ Sở Dữ Liệu Với ADO.NET

Cách Kết Nối Cơ Sở Dữ Liệu Với ADO.NET

Công nghệ ngày càng phát triển theo thời gian, nhiều framework cũng từ đó mà được sinh ra, cách kết nối cơ sở dữ liệu cũng không ngoại lệ. Hiện nay có nhiều framework miễn phí hỗ trợ chúng ta kết nối cơ sở dữ liệu như Entity Framework, Dapper, NHibernate... Việc sử dụng những framework trên giúp chúng ta tiết kiệm được rất nhiều thời gian.

Vậy trước kia, những lập trình viên sử dụng công nghệ gì để hỗ trợ kết nối cơ sở dữ liệu? Hôm nay mình xin chia sẻ với các bạn một kỹ thuật kết nối tới cơ sở dữ liệu đó chính là ADO.NET

1. ADO.NET là gì?

ADO.NET là tập hợp các thư viện được cung cấp bởi Microsoft nhằm giúp cho ứng dụng có thể thao tác (select, insert, update, delete) tới cơ sở dữ liệu (CSDL) bất kỳ như Sql Server, Oracle, MySql, SQLite...

2. Kiến trúc của ADO.NET

ADO.NET được chia thành 2 thành phần chính là Data ProviderData Set như hình vẽ dưới đây



2.1. Data Provider

Gồm các thư viện cung cấp cách thức kết nối tới CSDL và thao tác tới dữ liệu. Mặc định .Net cung cấp sẵn cho chúng ta thư viện để kết nối tới Sql Server - SqlClient (System.Data). Để sử dụng các loại cơ sở dữ liệu khác chúng ta có thể tải về từ Nuget.

Một số từ khóa bạn có thể sử dụng để tìm kiếm như: mysql provider (provider cho Mysql); sqlite provider (provider cho SqlLite); ...

2.2. Data Set

Gồm các thư viện để quản lý dữ liệu như DataTable (đại diện cho các bảng có trong CSDL); DataColumn (đại diện cho các cột có trong bảng); DataRow (đại diện cho dữ liệu); khóa chính; Constraint (đại diện cho khóa ngoại của bảng); DataRelation (đại diện cho mỗi quan hệ giữa các bảng)...

3. ADO.NET dùng để làm gì?

Mình sẽ tạo ra class AdoNetCSharp để làm ví dụ xuyên suốt trong bài viêt và được gọi trong hàm main như sau:

static void Main(string[] args)
        {
            var adoNet = new AdoNetCSharp();
            adoNet.Run();

            Console.ReadKey();
        }


Giả sử mình có CSDL tại IP là "127.0.0.1"; tên CSDL là "ADONETEXAMPLE"; tài khoản kết nối là "testado" và mật khẩu là "testado.net". Trong CSDL có bảng "Users" gồm các trường và dữ liệu như hình vẽ dưới đây:





Có 1 view là vwUsers



Có 2 store procedure là spGetAllUsers - xác định danh sách các user có trong bảng Users, spGetUserByUsername - xác định danh sách các user có Username giống với tham số @Username truyền vào.





3.1. Chuỗi kết nối cơ sở dữ liệu

Chuỗi chứa thông tin kết nối tới cơ sở dữ liệu như địa chỉ chứa CSDL, tên CSDL, thông tin tài khoản kết nối tới CSDL...
Cấu trúc của chuỗi kết nối CSDL như sau:

keyword1=value1; keyword2=value2;


Trong đó keywork không phân biệt chữ hoa, chữ thường; value tùy thuộc vào giá trị mà sẽ có sự phân biệt. Mình sẽ liệt kê một số các keyword hay được sử dụng ở bảng dưới đây.

Keyword Mô tả
Connect Timeout / Timeout / Connection Timeout Thời gian timeout khi thực hiện kết nối. Giá trị mặc định là 15 (giây).

Lưu ý: không nên thay đổi giá trị này. Nếu bạn để nhỏ hơn thì rất dễ xảy ra timeout. Nếu để lớn hơn thì connection sẽ được giữ lại và ảnh hưởng tới những connection phía sau.
Data Source / Server / Address/Addr Địa chỉ CSDL. Trong trường hợp bạn sử dụng cổng khác với cổng mặc định thì thêm ",port" và phía sau giá trị.
Initial Catalog/Database Tên CSDL.
User ID/UID Tên tài khoản đăng nhập.
Password/PWD Mật khẩu đăng nhập.
Application Name Thiết lập tên ứng dụng kết nối tới CSDL. Mặc định nếu không có sẽ là ".Net SqlClient Data Provider".

Lưu ý: nên sử dụng giá trị này để dễ dàng xác định khi thực hiện truy vấn log. Bạn sẽ thấy hữu ích khi sử dụng phần mềm SQL Server Profiler.
MultipleActiveResultSets Thiết lập thao tác tới CSDL trên cùng một connection. Giá trị là True hoặc False (giá trị mặc định).
Pooling Thiết lập cơ chế tái sử dụng connection.
Min Pool Size Số connection tối thiểu có trong pool (có thể dịch là tập hợp các connection).
Max Pool Size Số connection tối đa có trong pool.


Ví dụ chuỗi kết nối như sau:

var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";

Ngoài cách thiết lập chuỗi kết nối như trên, chúng ta có thể tạo chuỗi kết nối bằng cách sử dụng class SqlConnectionStringBuilder (System.Data.SqlClient)

var connectionStringBuilder = new SqlConnectionStringBuilder();
connectionStringBuilder["Server"] = "127.0.0.1";
connectionStringBuilder["Initial Catalog"] = "ADONETEXAMPLE";
connectionStringBuilder["User ID"] = "testado";
connectionStringBuilder["Password"] = "testado.net";
connectionStringBuilder["Application Name"] = "Test ADP.NET";

var connectionString = connectionStringBuilder.ToString();


3.2. Kết nối

Để kết nối tới CSDL, chúng ta dùng class SqlConnection như đoạn code dưới đây

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                //  TODO:...
            }
        }
    }


Bất kể bạn muốn làm gì trên CSDL, bạn cần thực hiện Open trước. Sau khi dùng sau, bạn phải nhớ Close connection lại. Trong ví dụ trên, bạn không thấy hàm Close nhưng khi bạn sử dụng using, .Net đã làm giúp bạn điều đó.

3.3. Truy vấn CSDL

Để truy vấn một bảng, chúng ta sử dụng đoạn code sau:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "Select * From Users";

                    var reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        var sUserId = reader["UserId"].ToString();
                        var username = reader["Username"].ToString();
                        var password = reader["Password"].ToString();
                        var fullName = reader["Fullname"] != null ? reader["Fullname"].ToString() : string.Empty;
                        var sState = reader["State"].ToString();

                        Console.WriteLine("{0}=={1}=={2}=={3}=={4}", sUserId, username, password, fullName, sState);
                    }
                }
            }
        }
    }


Trong ví dụ trên, chúng ta sử dụng câu lệnh sql là "Select * From Users" để thực hiện truy vấn dữ liệu. Đây là câu lệnh truy vấn trực tiếp đến bảng Users. Chúng ta có thể sử dụng view để truy vấn bằng cách thay câu lệnh sql thành "Select * From vwUsers".

Ngoài việc sử dụng câu lệnh thuần sql (giá trị mặc định là enum System.Data.CommandType.Text), chúng ta có thể sử dụng store procedure để thực hiện việc lấy dữ liệu. Để thực hiện lấy thông tin qua store procedure, chúng ta thay bằng đoạn code sau:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                
                using (var command = connection.CreateCommand())
                {
                    command.CommandType = CommandType.StoredProcedure;
                    command.CommandText = "spGetAllUsers";

                    var reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        var sUserId = reader["UserId"].ToString();
                        var username = reader["Username"].ToString();
                        var password = reader["Password"].ToString();
                        var fullName = reader["Fullname"] != null ? reader["Fullname"].ToString() : string.Empty;
                        var sState = reader["State"].ToString();

                        Console.WriteLine("{0}=={1}=={2}=={3}=={4}", sUserId, username, password, fullName, sState);
                    }
                }
            }
        }
    }


Bạn cần thêm CommandTypeStoreProcedure (kiểu enum System.Data.CommandType) để xác định thực hiện bằng store. Tên store sẽ nằm trong thuộc tính CommandText.

Kết quả của những đoạn code trên:



Trong trường hợp, chúng ta muốn tìm kiếm user có Username định dạng giống với tham số truyền vào thì đoạn code như sau:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                
                using (var command = connection.CreateCommand())
                {
                    var likeUsername = "XXX";

                    command.CommandText = "Select * From Users Where Username Like '" + likeUsername + "%'";

                    var reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        var sUserId = reader["UserId"].ToString();
                        var username = reader["Username"].ToString();
                        var password = reader["Password"].ToString();
                        var fullName = reader["Fullname"] != null ? reader["Fullname"].ToString() : string.Empty;
                        var sState = reader["State"].ToString();

                        Console.WriteLine("{0}=={1}=={2}=={3}=={4}", sUserId, username, password, fullName, sState);
                    }
                }
            }
        }
    }


Kết quả cho như sau:



3.4. Insert/Update/Delete

Về cơ bản đoạn code để thực hiện 3 lệnh trên là giống nhau, mình chỉ đưa ra ví dụ thực hiện lệnh Insert, các bạn có thể làm tương tự với các lệnh Update và Delete.

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                
                using (var command = connection.CreateCommand())
                {
                    var username = "XXX03";
                    var password = "Xxx03";
                    var fullName = "X3";
                    var state = "True";

                    command.CommandText = "Insert Into Users (Username, Password, Fullname, State) " +
                        "Values (N'" + username + "', N'" + password + "', N'" +  fullName + "', '" + state + "')";

                    var affectedRow = command.ExecuteNonQuery();

                    Console.WriteLine(affectedRow); //  Output: 1
                }
            }
        }
    }


Kết quả trong CSDL sẽ có bản ghi mới như sau:



4. Lưu ý

4.1. Kiểm tra dữ liệu khi đọc thông tin

Cùng xem lại đoạn code sau:

while (reader.Read())
{
	var sUserId = reader["UserId"].ToString();
	var username = reader["Username"].ToString();
	var password = reader["Password"].ToString();
	var fullName = reader["Fullname"] != null ? reader["Fullname"].ToString() : string.Empty;
	var sState = reader["State"].ToString();

	Console.WriteLine("{0}=={1}=={2}=={3}=={4}", sUserId, username, password, fullName, sState);
}


Trong cấu trúc bảng Users, trường UserId, Username, Password và State là những trường bắt buộc nhập nên việc kiểm tra dữ liệu lúc đọc là không cần thiết (đã được kiểm tra trước khi Insert); trường Fullname là trường cho phép NULL nên bắt buộc phải kiểm tra dữ liệu. Điều này nói lên rằng, chúng ta không được quá lạm dụng việc kiểm tra dữ liệu (vì mỗi lần kiểm tra dữ liệu là một lần CPU hoạt động).

4.2. Lưu ý khi sử dụng MultipleActiveResultSets

Là một tính năng chỉ làm việc với Sql Server.

- Chỉ nên sử dụng tính năng này đối với những ứng dụng có xử lý song song để tận dụng connection cho nhiều thao tác.

4.3. Lưu ý khi sử dụng pool

- Để thao tác với CSDL, chúng ta cần thực hiện Open - Close connection. Nếu quá trình Open - Close này xảy ra liên tục sẽ ảnh hưởng tới hiệu năng của chương trình. Để tránh việc Open - Close liên tục như vậy, .Net cung cấp khái niệm là pool - chứa các connection. Việc lựa chọn connection nào sẽ được thực hiện khi khởi tạo SqlConnection.

- .Net sử dụng connection string để phân biệt các connection có trong pool. Chúng ta hãy xem ví dụ sau:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                //  TODO: Connection đầu tiên được tạo tại đây.
            }

            connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado1; Password=testado1.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                //  TODO: Connection thứ 2 được tạo tại đây.
            }

            connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                //  TODO: Tái sử dụng lại connection đầu tiên
            }
        }
    }


Điều này có thể hiểu rằng, hiện tại trong pool có 2 connection được chờ sẵn.

4.4. Sử dụng tham số (Parameters) trong câu lệnh thao tác CSLD

- Đối với những thao tác có truyền tham số, chúng ta cần sử dụng Parameters để tránh việc bị tấn công Sql Injection. Các ví dụ phía trên (tìm kiếm user với Username có định dạng bất kỳ; thêm mới bản ghi vào bảng Users) mình viết dưới dạng câu lệnh thường nhưng trên thực tế thì không được phép viết như vậy.

- Đối với chức năng tìm kiếm user với Username có định dạng bất kỳ cần thực hiện như đoạn code dưới đây:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();

                using (var command = connection.CreateCommand())
                {
                    var likeUsername = "XXX";

                    command.CommandText = "Select * From Users Where Username Like @Username";
                    command.Parameters.Add(new SqlParameter("@Username", "" + likeUsername + "%"));

                    var reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        var sUserId = reader["UserId"].ToString();
                        var username = reader["Username"].ToString();
                        var password = reader["Password"].ToString();
                        var fullName = reader["Fullname"] != null ? reader["Fullname"].ToString() : string.Empty;
                        var sState = reader["State"].ToString();

                        Console.WriteLine("{0}=={1}=={2}=={3}=={4}", sUserId, username, password, fullName, sState);
                    }
                }
            }
        }
    }


- Đối với chức năng Insert vào CSDL thì đoạn code phải là:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();

                using (var command = connection.CreateCommand())
                {
                    var username = "XXX03";
                    var password = "Xxx03";
                    var fullName = "X3";
                    var state = "True";

                    command.CommandText = "Insert Into Users (Username, Password, Fullname, State) Values (@Username, @Password, @Fullname, @State)";
                    command.Parameters.Add(new SqlParameter("@Username", username));
                    command.Parameters.Add(new SqlParameter("@Password", password));
                    command.Parameters.Add(new SqlParameter("@Fullname", fullName));
                    command.Parameters.Add(new SqlParameter("@State", state));

                    var affectedRow = command.ExecuteNonQuery();

                    Console.WriteLine(affectedRow); //  Output: 1
                }
            }
        }
    }

4.5. Sử dụng Transtraction

Đối với những trường hợp chúng ta cần thực hiện nhiều thao tác phải đảm bảo an toàn dữ liệu, chúng ta cần sử dụng Transaction như code dưới đây:

public class AdoNetCSharp
    {
        public void Run()
        {
            var connectionString = "Server=127.0.0.1; Initial Catalog=ADONETEXAMPLE; User ID=testado; Password=testado.net; Application Name=Test ADP.NET";
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();

                var transaction = connection.BeginTransaction();

                try
                {
                    using (var command = connection.CreateCommand())
                    {
                        command.Transaction = transaction;
                        //  TODO: Thao tác 1
                    }

                    using (var command = connection.CreateCommand())
                    {
                        command.Transaction = transaction;
                        //  TODO: Thao tác 2
                    }

                    transaction.Commit();
                }
                catch (Exception)
                {
                    transaction.Rollback();
                }
            }
        }
    }


Khi đã bắt đầu transaction thì kết thúc phải là Commit (thực hiện thành công) hoặc Rollback (thực hiện lỗi và đưa dữ liệu về dữ liệu trước khi thực hiện các thao tác).

Kết luận

Mình đã giới thiệu tới các bạn toàn bộ hiểu biết của mình về ADO.NET. Rất mong bài viết sẽ có ích cho các bạn.