Trong trìn tự truy xuất cơ sở dữ liệu, bạn cần cung cấp các
thông số kết nối, chẳng hạn như thiết bị mà cơ sở dữ liệu đang chạy, và khả năng
đăng nhập của bạn. Bất kì ai đã từng làm việc với ADO sẽ dễ dàng quen với các
lớp kết nối của .NET, OleDbConnection và
SqlConnection:
Đoạn mã sau đây mô tả cách để tạo, mở và đóng một kết nối đến cơ
sở dữ liệu Northwind. Các ví dụ trong chương này chúng
ta dùng cơ sở dữ liệu Northwind, được cài đặt chung với
các ví dụ của .NET Framework SDK:
using System.Data.SqlClient; string source = "server=(local)\\NetSDK;" + "uid=QSUser;pwd=QSPassword;" + "database=Northwind"; SqlConnection conn = new SqlConnection(source); conn.Open(); // Do something useful conn.Close();
Chuỗi kết nối sẽ trở nên thân thiện nếu bạn đã từng dùng ADO hay
OLE DB trước đây - thật vậy, bạn có thể cắt và dán từ mã cũ của bạn, nếu bạn
dùng OleDb provider. Trong ví dụ chuỗi kết nối này, các
tham số được dùng như sau (các tham số cách nhau bởi dấu chấm phẩy trong chuỗi
kết nối).
-
server=(local)\\NetSDK - Nó biểu diễn database server được kết nối. SQL Server cho phép một số các tiến trình database server processes khác nhau chạy trên cùng một máy, vì vậy ở đây chúng ta thực hiện kết nối với tiến trình NetSDK trên máy cụ bộ.
-
uid=QSUser - Tham số này mô tả người dùng cơ sở dữ liệu. Bạn cũng có thể sử dụng User ID.
-
pwd=QSPassword - và đây là password cho người dùng đó. .NET SDK là một bộ các cơ sở dữ liệu giống nhau, và user/password này được liên kết và được thêm vào trong quá trình cài đặt các ví dụ .NET. Bạn cũng có thể dùng Password.
-
database=Northwind - Cái này mô tả loại dữ liệu để kết nối - mỗi tiến trình SQL Server có thể đưa ra một vài loại dữ liệu khác nhau.
Ví trên mở một kết nối cơ sở dữ liệu ùng chuỗi kết nối đã được
định nghĩa, sau đó đóng kết nối lại. Khi kết nối đã được mở, bạn có thể phát các
lệnh để thao tác trên cơ sở dữ liệu, và khi hoàn tất, kết nối có thể được đóng
lại.
SQL Server có một chế độ bảo mật khác - nó có thể dùng chế độ bảo
mật của Windows, vì thế các khả năng truy cập của Windows có thể truyền cho SQL
Server. Với lựa chọn này bạn có thể bỏ đi các vị trí uid và pwd trong chuỗi kết nối, và
thêm vào Integrated Security=SSPI.
Trong lúc download mã nguỗn sẵn có cho chương này, bạn cần tìm
file Login.cs nó đơn giãn hóa các ví dụ trong chương
này. Nó được kết nối với tất cả các mã ví dụ, và bao gồm thông tin kết nối cơ sở
dữ liệu dùng cho các ví dụ; sau đó bạn có thể cung cấp tên server, user, and
password một cách thích hợp. Nếu mặc định dùng Windows integrated security; bạn
cần thay đổi username và password cho phù hợp.
Bây giờ chúng ta đã biết cách mở các kết nối, trước khi chuyển qua
vấn đề khác chúng ta cần xem xét một vài thực hành tốt có liên quan đến các kết
nối.
Sử dụng hiệu quả các Connection
Một cách tổng quát, khi sử dụng các tài nguyên "hiếm" trong
.NET, chẳng hạn như các kết nối cơ sở dữ liệu, các cửa sổ,hoặc các đối tượng đồ
họa, tốt hơn hết bạn nên đảm bảo rằng các tài nguyên này luôn phải được đóng lại
sau khi đã sử dụng xong. Dù vậy các nhà thiết kết của .NET có thể làm điều này
nhờ trình thu gom rác, nó luôn làm sau bộ nhớ sau một khoảng thời gian nào đó,
tuy nhiên nó nên được giải phóng càng sớm càng tốt.
Rõ ràng là khi viết mã truy xuất một cơ sở dữ liệu, việc giữ một
kết nối càng ít thời gian càng tốt để không làm ảnh hưởng đến các phần khác.
Trong nhiều tình huống tiêu cực, nếu không đóng một kết nối có thể khoá không
cho các người dùng khác truy nhập vào các bảng dữ liệu đó, một tác hại to lớn
đối với khả năng thực thi của ứng dụng. Việc đóng một kết nối cơ sở dữ liệu có
thể coi là bắt buộc, vì thế ứng dụng này chỉ ra cách cấu trúc mã của bạn để giảm
thiểu các rủi ro cho một mã nguồn mở.
Có hai cách để đảm bảo rằng các kết nối cơ sở dữ liệu được giải
phóng sau khi dùng.
Tùy chọn một - try/catch/finally
Tùy chọn thứ nhất để đảm bảo rằng các tài nguyên được dọn
sạch là sử dụng các khối lệnh try…catch…finally, và đảm bảo rằng bạn đã đóng các kết nối trong khối
lệnh finally. Đây là một ví dụ nhỏ:
try { // Open the connection conn.Open(); // Do something useful } catch ( Exception ex ) { // Do something about the exception } finally { // Ensure that the connection is freed conn.Close ( ) ; }
Với khối kết nối bạn có thể giải phóng bất kì tài nguyên nào mà
bạn đã dùng. Vấn đề duy nhất trong phương thức này là bạn phải bảo đảm rằng bạn
có đóng các kết nối - rất là dễ quên việc thêm vào khối finally, vì vậy một phong cách lập trình tốt rất quan
trọng.
Ngoài ra, bạn có thể mở một số tài nguyên (chẳng hạn hai kết
nối cơ sở dữ liệu và một file) trong một phương thức, vì vậy đôi khi các khối
try…catch…finally trở nên khó đọc. Có một cách khác để đảm bảo rằng các
tài nguyên được dọn dẹp - sử dụng câu lệnh.
Tùy chọn hai - Sử dụng khối câu lệnh
Trong lúc phát triển C#, phương thức .NET's dọn dẹp các đối
tượng khi chúng không còn được tham chiếu nữa sử dụng các huỷ bất định trở thành
một vấn đề nóng hổi. Trong C++, ngay khi một đối tượng rời khỏi tầm vực, khối
huỷ tử của nó sẽ tự động được gọi. Nó là một điều rất mới cho các nhà thiết cớ
các lớp sử dụng tài nguyên, khi một huỷ tử được sử dụng để đóng các tài nguyên
nếu các người dùng quên làm điều đó. Một huỷe tử C++ được gọi bất kì khi nào một
đối tượng vượt quá tầm vực của nó - vì vậy khi một ngoại lệ được phát ra mà
không được chặn, tât cả các hủy tử cần phải được gọi.
Với C# và các ngôn ngữ có quản khác, tất cả đều tự động, các khối
huỷ tử định trước được thay thế bởi trình thu gom rác, cái được dùng để tháo các
tài nguyên tại một thời điểm trong tương lai. Chúng mang tính bất định, nghĩa là
bạn sẽ không biết trước được khi nào thì việc đó sẽ xảy ra. Nếu quên không đóng
một kết nối cơ sở dữ liệu có thể là nguyên nhân gây ra lỗi khi chạy trong .NET.
Mã sau đây sẽ giải thích cách để sử dụng giao diện IDisposable (đã được bàn kĩ
trong chương 2) để giải phóng tài nguyên khi thoát khỏi khối using .
string source = "server=(local)\\NetSDK;" + "uid=QSUser;pwd=QSPassword;" + "database=Northwind"; using ( SqlConnection conn = new SqlConnection ( source ) ) { // Open the connection conn.Open ( ) ; // Do something useful }
Mệnh đề using đã được giới thiệu trong
chương 2. Đối tượng trong mệnh đề using phải thực thi
giao diện IDisposable, nếu không một se tạo ra một lỗi
biên dịch. Phương thức Dispose() sẽ tự động được gọi
trong khi thoát khỏi khối using.
Khi xem mã IL của phương thưc Dispose()
của SqlConnection (và OleDbConnection), cả hai đều kiểm tra trạng thái của đối
tượng kết nối, và nếu nó đang mở phương thức Close() sẽ
được gọi.
Khi lập trình bạn nên dùng cả hai tùy chọn trên.Ở nhưng chỗ bạn
cần các tài nguyên tốt nhất là sử dụng mệnh đề using(),
dù vậy bạn cũng có thể sử dụng câu lệnh Close(), nếu
quên không sử dụng thì khối lệnh using sẽ đóng lại giúp bạn. Không gì có thể
thay thế được mọt bẫy ngoại lệ tốt, vì thế tốt nhất bạn dùng trộn lẫn hai phương
thức như ví dụ sau:
try { using (SqlConnection conn = new SqlConnection ( source )) { // Open the connection conn.Open ( ) ; // Do something useful // Close it myself conn.Close ( ) ; } } catch (Exception e) { // Do something with the exception here... }
Ở đây tôi đã gọi tường minh phương thức Close()
mặc dù điều đó là không bắt buộc vì khối lệnh using đã làm điều đó thay cho bạn; tuy nhiên, bạn luôn chắc
rằng bất kì tài nguyên nào cũng được giải phóng sớm nhất có thể - bạn có thể có
nhiều mã trong khối lệnh mã không khoá tài nguyên.
Thêm vào đó, nếu một ngoại lệ xảy ra bên trong khối using, thì phương thức IDisposable.Dispose sẽ được gọi để bảo đảm rằng tài nguyên
được giải phóng, điều này đảm bảo rằng kết nối cơ sở dữ liệu luôn luôn được đóng
lại. Điều này làm cho mã dễ đọc và luôn đảm bảo rằng kết nối luôn được đóng khi
một ngoại lệ xảy ra.
Cuối cùng, nếu bạn viết các lớp bao bọc một tài nguyên có lẽ
luôn thưc hiện giao diện IDisposable để đóng tài
nguyên. Bằng cách dùng câu lệnh using() nó luôn đảm bảo
rằng tài nguyên đó sẽ được dọn dẹp.
Các Transaction (giao dịch)
Thường khi có nhiều hơn một cập nhật dữ cơ sở dữ liệu thì
các thực thi này được thực hiện bên trong tầm vực của một transaction. Một
transaction trong ADO.NET được khởi tạo bằng một lời gọi đến các phương thức
BeginTransaction() trên đối tượng kết nối cơ sở dữ
liệu. Những phương thức này trả về một đối tượng có thể thực thi giao diện IDbTransaction, được định nghĩa trong System.Data.
Chuỗi mã lệnh dưới đây khởi tạo một transaction trên một kết nối
SQL Server:
string source = "server=(local)\\NetSDK;" + "uid=QSUser;pwd=QSPassword;" + "database=Northwind"; SqlConnection conn = new SqlConnection(source); conn.Open(); SqlTransaction tx = conn.BeginTransaction(); // Execute some commands, then commit the transaction tx.Commit(); conn.Close();
Khi bạn khởi tạo một transaction, bạn có thể chọn bậc tự do cho
các lệnh thực thi trong transaction đó. Bậc này chỉ rõ sự tự do của transaction
này với các transaction khác xảy ra trên cơ sở dữ liệu. Các hệ cơ sở dữ liệu có
thể hỗ trợ bốn tùy chọn sau đây:
Isolation Level
|
Description
|
---|---|
ReadCommitted
|
Mặc định cho. Bậc này đảm bảo rằng dữ liệu đã ghi bởi
transaction sẽ chỉ có thể truy cập được bởi một transaction khác sau khi nó hoàn
tất công việc của mình.
|
ReadUncommitted
|
Tùy chọn này cho phép transaction của bạn có thể đọc dữ liệu
trong cơ sở dữ liệu, dù cho dữ liệu đó đang được một transaction khác sử dụng.
Ví dụ như, nều hai người dùng truy cập cùng lúc vào một cơ sở dữ liệu, và người
thứ nhất chièn một vài dữ liệu trong transaction của họ (đó là một Commit hoặc Rollback), và người thứ
hai với tuỳ chọn bậc tự do là ReadUncommitted có thể
đọc dữ liệu.
|
RepeatableRead
|
Bậc này là một mở rộng của ReadCommitted, nó bảo đảm rằng nếu một lệnh tương tự được
phát ra trong transaction, ensures that if the same statement is issued within
the transaction, regardless of other potential updates made to the database, the
same data will always be returned. This level does require extra locks to be
held on the data, which could adversely affect performance.
This level guarantees that, for each row in the initial
query, no changes can be made to that data. It does however permit "phantom"
rows to show up - these are completely new rows that another transaction may
have inserted while your transaction is running.
|
Serializable
|
This is the most "exclusive" transaction level, which in
effect serializes access to data within the database. With this isolation level,
phantom rows can never show up, so a SQL statement issued within a serializable
transaction will always retrieve the same data.
The negative performance impact of a Serializable transaction should not be underestimated - if
you don't absolutely need to use this level of isolation, it is advisable to
stay away from it.
|
Bậc tự do mặc định của The SQL Server, ReadCommitted, là một kết hợp tốt giữa tính chặc chẽ và sẵn
dùng của dữ liệu. Tuy nhiêu, có nhiều trường hợp bậc tự do có thể tăng lên,
trong .NET bạn đơn giản khởi tạo một transaction khác với bậc khác bậc mặc định.
Điều đó còn dựa vào kinh nghiệm của mỗi người.
Một điếu cuối cùng về transactions - nếu bạn đang dùng một cơ sở dữ liệu không hỗ trợ các transaction, thì tốt nhất bạn nên đổi sang một cơ sở dữ liệu có dùng chúng!