![]() |
USOL Vietnam .NET Center Ojima Ryoji and Nguyễn Huyền Nhung |
ADO.NET 2.0 class library dùng để truy cập dữ liệu mà .NET Framework 2.0 cung cấp. Nếu sử dụng tốt ADO.NET 2.0 (và Visual Studio 2005) thì có thể giảm đáng kể chi phí cần thiết cho việc tạo Business Entity và Data Access Logic Component. (Trong tài liệu này cũng giải thích một cách đơn giản về 2 thuật ngữ Business Entity và Data Access Logic Component. Về chi tiết xin tham khảo địa chỉ sau Application Architecture for .NET).
Điều quan trọng khi tạo Business Entity là sử dụng Visual Studio 2005 để tạo Typed-DataSet, tạo cấu trúc Class giống với cấu trúc Table của RDBMS (Relational Database Management System). Nếu sử dụng nguyên DataSet class của System Data Namespace thấy trong sách vở, thì không thể thỏa mãn được yêu cầu về business application.
Điều quan trọng khi tạo Data Access Logic Component là sử dụng Visual Studio 2005, để tạo tự động TableAdapter. Nếu TableAdapter được sinh tự động bằng Visual Studio 2005 thì source code mở kết nối dữ liệu, tạo câu lệnh SQL sẽ tự động được sinh ra cho nên sẽ giảm được công sức. Hơn hết, có thể tránh được việc Logic để truy cập data được viết trong presentation như Windows Forms và ASP.NET, và khi phát sinh trùng sẽ không thể bảo trì.
Sau đây chúng tôi xin mô tả chi tiết hơn về những vấn đề này.
Business Entity là class hiển thị Data được xử lý trong business application. Tất cả business application, không có ngoại lệ, sẽ xử lí data. Ví dụ như trong phân phối sản phẩm thì nó là thông tin sản phẩm, thông tin khách hàng, thông tin lưu kho... Microsoft gọi class group để xử lí các data một cách hiệu quả và thống nhất là Business Entity. (Mặt khác, class group có vai trò tương tự trong Community của phân tích/thiết kế hướng đối tượng cũng được gọi là Domain Object).
Có 2 phương pháp tạo Business Entity trong .NET Framework 2.0. Đó là phương pháp sử dụng nguyên DataSet class, và phương pháp sử dụng Visual Studio 2005 để cho sinh tự động Typed-DataSet. (Phương pháp tạo class độc lập trong Java...cũng được xem xét, tuy nhiên nếu làm như vậy thì sẽ mất đi ý nghĩa của việc sử dụng .NET Framework 2.0. Vì thế trong tài liệu này sẽ bỏ qua phương pháp đó.)
Tôi nghĩ rằng trong 2 cách lựa chọn trên USOL-Việt Nam cần phải lựa chọn Typed-DataSet. Sau đây, chúng tôi xin trình bày các vấn đề của phương pháp sử dụng nguyên DataSet class, và cách Typed-DataSet giải quyết những vấn đề đó.
Trong tài liệu này sẽ sử dụng ER Diagram như dưới để làm rõ vấn đề này.

Dưới đây là source code cụ thể cho phương pháp sử dụng nguyên DataSet class. Nếu compile rồi thực hiện source code dưới, thì sẽ output ra màn hình console những sản phẩm có giá trị dưới $2000.00.
// Tạo DataSet
using (DataSet ds = new DataSet())
{
// Tạo connection.
using (SqlConnection sc = new SqlConnection(
"Data Source=localhost\\SQLExpress; Initial Catalog=UsolV; Integrated Security=true;"))
{
// Tạo DataAdapter.
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Product where price <= 2000.00", sc))
{
// Sử dụng DataAdapter, để lấy data của Product Table.
sda.Fill(ds, "Product");
}
}
// Xác nhận việc lấy dữ liệu đúng.
// Tất cả DataRow của Product Table đã lấy.
foreach (DataRow dr in ds.Tables["Product"].Rows)
{
// Output ra màn hình Console
Console.WriteLine("{0}\r\n{1}\r\n{2:c}\r\n", dr["Name"], dr["Description"], dr["Price"]);
}
}
(Viết ra chỉ nhằm mục đích tham khảo) using là từ dành riêng để gọi tự động Dispose() method. Việc giải phóng memory trong .NET Framework 2.0 được garbage collection làm tự động, tuy nhiên không tự động giải phóng trong trường hợp đã sử dụng resource như kết nối thông tin. Đối với business application có yêu cầu độ tin cậy, trong trường hợp sử dụng resource ngoài bộ nhớ thì bắt buộc phải gọi Dispose()method. Đây là việc rất phức tạp (để có thể gọi được Dispose()method ngay cả khi phát sinh ngoại lệ thì phải viết try/finally), nên từ khóa using được sử dụng.
Nếu chỉ nhìn qua thì sẽ thấy source code trên không có vấn đề gì. Nếu Data Binding trên GridView của Windows Forms thì vẫn có thể xử lí data. Nếu chỉ có 1 table, thì cũng không có vấn đề lớn gì xảy ra cho dù áp dụng phương pháp sử dụng nguyên DataSet.
Phương pháp được nghĩ tới đầu tiên trong trường hợp 1 table bị chia thành 2 table là phương pháp JOIN. Câu lệnh SELECT trong phần source code trên đã thay đổi như dưới đây.
select
Product.Name, Product.Description, Product.Price, Category.Name
from
Product
inner join Category ON Product.CategoryIdentity = Category.[Identity]
where
Price <= 2000.00
Với cách làm này, cho dù chia thành nhiều Table cũng không có vấn đề gì. Mặc dù gắn điều kiện nếu chỉ hiện thị data lên màn hình.Tuy nhiên với phương pháp này sẽ rất khó khăn trong việc update data.
Lấy kết quả đã JOIN và hiển thị ra bảng. Nếu trong trường hợp câu lệnh SELECT như trên thì sẽ hiển thị ra bảng như dưới đây. (Để thu gọn màn hình sẽ lược bỏ Product.Description.)
| Product.Name | Product.Price | Category.Name |
|---|---|---|
| ThinkPad T61 | $2200.00 | Lenovo ThinkPad Series |
| ThinkPad X60S | $1200.00 | Lenovo ThinkPad Series |
| VAIO Type U | $1500.00 | Sony VAIO Series |
Chúng ta sẽ thử cùng suy nghĩ. Nếu thay Category.Name của dòng đầu tiên thành "IBM ThinkPad Series" thì sẽ như thế nào? Có update Name của Category Table không? Nếu để nguyên là "Lenovo ThinkPad Series" thì dòng thứ 2 sẽ không bị chỉnh sửa? Nếu xóa đi dòng thứ 3 thì category Sony VAIO Series sẽ như thế nào?có bị xóa đi không? Thì có lẽ chỉ thình thoảng mới thấy trên màn hình xuất hiện sản phẩm Sony VAIO Series với giá >=2000$.
Với lý do trên, sẽ không thể sử dụng JOIN trong trường hợp update. Hầu hết trong các business application đều có Update Data. Vì thế, trong business application mà chỉ dựa vào JOIN thì rất nguy hiểm.
Kĩ thuật viên Microsoft những người đã tạo .NET Framework 2.0 tất nhiên nắm được những điều đã trình bày đến đây. Vì vậy trên thực tế, trong DataSet đã chuẩn bị sẵn cấu trúc để có thể xử lí nhiều Table một cách tự nhiên. Dưới đây là source code cụ thể đã sử dụng function đó.
// Tạo DataSet.
using (DataSet ds = new DataSet())
{
// Tạo Connection.
using (SqlConnection sc = new SqlConnection(
"Data Source=localhost\\SQLExpress; Initial Catalog=UsolV; Integrated Security=true;"))
{
// Trước tiên, lấy tất cả các Category.
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Category", sc))
{
sda.Fill(ds, "Category");
}
// Lấy tất cả các sản phẩm có giá trị <= $2000.
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Product where Price <= 2000.00", sc))
{
sda.Fill(ds, "Product");
}
}
// Add thêm DataRelation liên kết category với product.
ds.Relations.Add(new DataRelation("Category_Product",
ds.Tables["Category"].Columns["Identity"], ds.Tables["Product"].Columns["CategoryIdentity"]));
// Đến đây, có thể lấy được Product Group thuộc vào Category bằng DataRow GetChildRows("Category_Product") của DataRow của Category,
// và có thể lấy được category có chứa product bằng GetParentRow("Category_Product") của DataRow của product.
// Xác nhận xem lấy dữ liệu có đúng không.
// Tất cả DataRow của product table đã lấy.
foreach (DataRow dr in ds.Tables["Product"].Rows)
{
// Output ra màn hình Console.
Console.WriteLine("{0}\r\n{1}\r\n{2:c}\r\n{3}\r\n",
dr["Name"], dr["Description"], dr["Price"], dr.GetParentRow("Category_Product")["Name"]);
// Vì có thể lấy được DataRow của category bằng GetParentRow("Category_Product")
// cho nên có thể hiển thị category name nếu là dr.GetParentRow("Category_Product")["Name"].
}
}
Point chính là việc gọi ra Add()method đối với Relations Property của DataSet. Nếu với cách viết source code như trên thì sẽ không phát sinh các vấn đề giống như trong trường hợp JOIN. Tuy nhiên là do chỉ có 1 dòng Category giống như trên RDBMS. (Trong source code trên có vấn đề là sẽ lấy tất cả, kể cả những Category không cần thiết. Về vấn đề này sẽ giải quyết bằng "Cách tạo Data Access Logic Component" sẽ mô tả ở phần sau.)
Với lí do trên, nếu làm DatSet một cách cẩn thận và sử dụng thì chắc là sẽ được. Chắc chắn sẽ được, nếu như chúng ta lập trình không phải là công việc mà chỉ là sở thích.
Tuy nhiên thì phương pháp sử dụng DataSet và DataRelation là phương pháp rất phiền toái. Trước hết, mỗi lần lấy data từ RDBMS thì việc add thêm DataRelation rất phiền toái. Hơn nữa thì cho đến đây, chúng tôi cũng đã bỏ qua vấn đề này, tuy nhiên thì việc viết kiểu như dataRow["ColumnName"] cũng rất phiền. Việc không đáng kể gì như thế này cũng trở thành vấn đề là bởi vì business application rất phức tạp đến mức không thể sử dụng GridView control , hiển thị lên màn hình và được người sử dụng input vào. Ví dụ như việc kiểm tra giá trị input, tính toán chi phí gửi, lựa chọn các sản phẩm được giới thiệu. Nếu muốn tạo một site về electric commerce giống như amazon.com thì buộc chúng ta phải viết source code để thực hiện những business logic phức tạp như thế này. Giả sử trong phần source code đó có viết dataRow["ColumnName"], thì sẽ tốn chi phí cho việc input do không thể sử dụng IntelliSense trong string, và sẽ tốn chi phí cho việc test do không biết được là column name đến khi thực hiện thử có đúng không?
Vậy thì nên làm thế nào? Vâng, hãy sử dụng Typed-DataSet mà USOL Việt Nam đề xuất.
Để hiểu được Typed-DataSet thì điều quan trọng số 1 là dùng thử nó. Hãy khởi động Visual Studio 2005, tạo project thích hợp, rightclick vào project mà đã tạo bằng [Solution Explorer], lựa chọn [Add] - [New Item...] từ pop up menu, lựa chọn [DataSet] icon tại hộp thoại [Add New Item] và click vào nút [OK]. Khi đó thì Typed-DataSet được tạo. Sau khi Typed-DataSet được tạo, hãy draft and drop table thích hợp từ [Server Explorer] như sơ đồ dưới đây.

Đến đây, thì việc tạo Typed-DataSet đã hoàn thành. Với những thao tác cho đến đây thì chúng ta có thể giải quyết kể cả vấn đề add thêm DataRelation mà trước đây cảm thấy rất phiền toái, và cả vấn đề bắt buộc phải viết là dataRow["ColumnName"].Với những căn cứ như vậy thì chúng ta hãy dùng Typed-DataSet và thử viết lại source code trên. Dưới đây là source code đã viết lại.
// Tạo Typed-DataSet.
using (CatalogDataSet ds = new CatalogDataSet())
{
// Về phần lấy data thì giống với trường hợp sử dụng nguyên DataSet.
using (SqlConnection sc = new SqlConnection(
"Data Source=localhost\\SQLExpress; Initial Catalog=UsolV; Integrated Security=true;"))
{
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Category", sc))
{
sda.Fill(ds, "Category");
}
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Product where Price <= 2000.00", sc))
{
sda.Fill(ds, "Product");
}
}
// Không cần add thêm DataRelation.
// Đã hoàn thành định nghĩa kiểu cho dòng của sản phẩm, hơn nữa cũng có thể lấy được DataTable bằng property của tên giống với tên table.
foreach (CatalogDataSet.ProductRow dr in ds.Product)
{
// Có thể lấy được Data bằng property của tên giống với tên colum.
// Ngoài ra cũng có thể lấy được category trực thuộc trong CategoryRow.
Console.WriteLine("{0}\r\n{1}\r\n{2:c}\r\n{3}\r\n",
dr.Name, dr.Description, dr.Price, dr.CategoryRow.Name);
// Ở đây chúng tôi không trình bày,
// nhưng có thể lấy được product group thuộc category bằng GetProductRows() của CategoryDataRow.
}
}
Nếu nhìn vào source code trên thì tôi nghĩ là mọi người có thể hiểu phần sau hết sức đơn giản. (còn phần phía trước cũng sẽ đơn giản nếu có thể sử dụng TableAdapter trình bày bằng 「phương pháp tạo Data Access Logic Component」). Khi đó sẽ không cần add thêm DataRelation, và nên viết dataRow.Name hơn là dataRow["Name"], và nên viết là dataRow.CategoryRow.Name hơn là viết dataRow.GetParentRow("Category_Product")["Name"] thì sẽ đơn giản hơn rất nhiều phải không? Vì có thể sử dụng được IntelliSense, hơn nữa dù tên cột bị sai đi nữa chỉ cần compile là có thể hiểu được.
Cơ chế (mechanism) của Typed-DataSet có trong việc Visual Studio 2005 tự sinh source code . Bạn hãy thử click vào [+] ở bên trái của Typed-DataSet trong [Solution Explorer] của Visual Studio 2005. Khi đó, chắc chắn sẽ hiện ra file "Typed-DataSet name.Designer.cs" như hình dưới đây.

Chính "Typed-DataSet name.Designer.cs" nnày là phần source code mà Visual Studio 2005 tự sinh. Nếu thử loại đi phần bên trong thì source code có add thêm DataRelation và source code access vào DataRow["Name"] trong property name sẽ được mô tả. Vì Visual Studio 2005 làm cho chúng ta tất cả những phần có thể làm một cách máy móc, vì vậy chúng ta chỉ tập trung vào những phần có tính chất sáng tạo, những phần thực hiện business logic trên source code.
Business application rất phức tạp. Phức tạp ở ngay cả việc phải sử dụng nhiều table. Vì thế nên sẽ biding data các DataTable có DataSet trong GridView control, hiển thị lên màn hình dưới dạng bảng, không thể thực hiện được nếu chỉ bằng phương pháp đơn giản như cho input/output. (Nếu như chỉ maintain 1 table thì làm bằng Microsoft Access sẽ nhanh và rẻ hơn nhiều so với làm chương trình bằng các Visual Studio 2005).
Bằng việc sử dụng JOIN trong câu lệnh SELECT, để làm thành 1 bảng, có thể giả coi như là chỉ có 1 bảng. Tuy nhiên trong business application thì update là chức năng đi kèm, nếu thử JOIN một lần thì sẽ không thể update được, vì thế không thể sử dụng được phương pháp này cho business application.
Cũng có thể phòng tránh bằng việc làm cẩn thận cách sử dụng Data Set. Vì có thể cho nhiều DataTable vào trong DataSet, hơn nữa còn có thể thiết lập mối quan hệ giữa DataTable đó bằng DataRelation. Với phương pháp này, thì data mà DataSet đang hiển thị sẽ có cấu trúc giống với data của RDBMS, vì thế đương nhiên có thể xử lí nhiều table, và update cũng đơn giản. Thế nhưng vì buộc phải nhập bằng tay table name, column name, relationship name cho nên vẫn chưa thỏa mãn về chi phí thực hiện và chi phí test.
Tất cả những vấn đề mà chúng tôi trình bày cho tới đây thì đều có thể giải quyết được bằng việc sử dụng Typed-DataSet. Typed-DataSet là phương thức mà Visual Studio 2005 tự sinh ra những property để lấy table, colum, relationship bằng những thao tác đơn giản như draft and drop table từ [Server Explorer]. Nếu như dùng phương pháp Typed-DataSet, thì với chi phí thấp nhất cũng có thể thực hiện phương pháp sử dụng tối ưu những tính năng của DataSet. Trong trường hợp muốn tạo business application nhất định hãy thử sử dụng Typed-DataSet này.
Data Access Logic Component là class che dấu thông tin với RDBMS. Trên tạp chí và Internet, cũng có thể thấy được source code mô tả trao đổi thông tin với RDBMS trong event handler của Windows Form và Web Form, nhưng trong business application thì không chấp nhận cách làm như thế. Nói như vậy là bởi vì , nếu mô tả trao đổi thông tin với RDBMS trong event handler tồn tại nhiều, thì sẽ phát sinh trùng lặp một lượng lớn. Nếu trùng lặp, thì khi đã thay đổi cấu trúc bảng...phải thay đổi nhiều vị trí. Là con người nên chắc chắn phải mắc lỗi, do đó sẽ trở thành hệ thống rất khó maintenance. Nếu delivery hệ thống như thế thì sau đó chắc chắn sẽ không nhận được order đến lần thứ 2.
Với lí do đó sẽ làm phần trao đổi thông tin với RDBMS thành class độc lập. Microsoft gọi nhóm class độc lập để trao đổi thông tin với RDBMS là Data Access Logic Component. ( cũng có người gọi là Data Access Object, nhưng nếu gọi như vậy thì sẽ trùng tên với kỹ thuật trao đổi thông tin với RDBMS trong thế hệ Visual Basic của Microsoft, do đó gần đây tên này không còn được sử dụng nữa).
Có 2 phương pháp để tạo Data Access Logic Component trong .NET Framework 2.0 là:phương pháp sử dụng y nguyên DataAdapter class và phương pháp sử dụng Visual Studio 2005 để tạo tự động TableAdapter. (cũng có phương thức sử dụng DataReader nhưng nó không gần gũi với Typed-DataSet đã ghi trong 「cách tạo Business Entity」nên trong này sẽ không đề cập đến.) Trong việc lựa chọn 2 kỹ thuật trên , USOL-V nên chọn TableAdapter. Dứơi đây sẽ trình bày những vấn đề của phương pháp sử dụng y nguyên DataAdapter Class, và TableAdapter sẽ giải quyết những vấn đề đó như thế nào.
Những lập trình viên, những người kiếm tiền bằng việc phát triển các business application như chúng ta không chỉ deliver binary sau khi compile, mà deliver cả source code trước khi compile. Bởi vì, sau khi deliver, để đối phó với những thay đổi của môi trường business thì cần phải thay đổi source code, khi đó người order sẽ phải xem. Chính vì vậy nên tính maintenance của source code rất quan trọng. Nếu delivery source code có tính maintenance thấp thì chúng ta sẽ không nhận được order lần thứ 2. Khi tiếng đồn đó lan rộng ra thì chúng ta chắc chắn cũng sẽ mất order từ công ty khác.
Vậy nên các lập trình viên như chúng ta phải luôn suy nghĩ xem là làm thế nào để nâng cao tính maintenance. Đặc biệt là cần phải loại bỏ triệt để trùng lặp có thể gây ra rắc rối lớn khi thay đổi. (Dù có một phần logic khó hiểu nhưng nếu phần đó không bị maintenance thì cũng không bị phát hiện. Nhưng trường hợp có nhiều trùng lặp, sẽ bị phát hiện ngay bằng regression test khi maintenance.) Ví dụ. cần phải tránh hoàn toàn tình trạng câu SQL nằm rải rác ở nhiều vị trí.
Để tránh sự trùng lặp này, hiệu quả nhất là phải phân chia class theo vai trò. Business Entity đã mô tả ở trên sẽ đảm nhiệm phần thể hiện data, còn Data Access Logic Component có trong chủ đề hiện nay sẽ đảm nhiệm phần trao đổi thông tin với RDBMS. Nếu làm như vậy thì chỉ cần xem Data Access Logic Component cũng có thể phán đoán có trùng lặp hay không trong trao đổi thông tin RDBMS. Chắc chắn việc chỉ xem Data Access Logic Component sẽ dễ dàng hơn so với việc xem Windows Form và Web Form của source code lớn tương ứng với nhiều chức năng. chắc chắn rằng chỉ xem Business Entity và. (Microsoft khuyến khích việc phân chia phần business logic thành Business Component, và phân chia trao đổi thông tin giữa client/ server thành Serivce Agent và Service Interface. Tương tự như vậy, Usol –V đang phân loại theo class như khuyến khích của Microsoft và giảm vùng thừa có trùng lặp.)
Để sử dụng làm xuất phát điểm cho buổi thảo luận này, trước hết chúng tôi sẽ đưa ra những source code chất lượng không tốt, những source code mà nếu deliver sẽ không nhận được order nữa. Đề tài là Web Form của ASP.NET, hình ảnh màn hình như sau.

*.aspx để thực hiện màn hình này như sau.
<%@ Page Language="C#" AutoEventWireup="true" Codebehind="UseDataAdapterWebForm.aspx.cs" Inherits="DataAccessLogicComponent.UseDataAdapterWebForm" %>
<%@ Import Namespace="DataAccessLogicComponent" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>using the DataAdapter</title>
</head>
<body>
<form id="form1" runat="server">
<asp:Repeater ID="catalogRepeater" DataSource='<%# CatalogDataSet.Category.Rows %>' runat="server">
<HeaderTemplate>
<table border="1px">
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td colspan="2">
<%# HttpUtility.HtmlEncode(Eval("Name", "{0}")) %>
</td>
</tr>
<asp:Repeater ID="productRepeater" DataSource='<%# ((CatalogDataSet.CategoryRow)Container.DataItem).GetProductRows() %>' runat="server">
<ItemTemplate>
<tr>
<td>
<%# HttpUtility.HtmlEncode(Eval("Name", "{0}")) %>
</td>
<td>
<%# HttpUtility.HtmlEncode(Eval("Price", "{0:c}")) %>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
</form>
</body>
</html>
Trong phần <%# ... %> của source code trên có sử dụng chức năng data binding của ASP.NET. Giải thích chi tiết về ASP.NET chúng tôi sẽ nói trong một dịp tới, còn ở đây, cho phép chúng tôi được giải thích một cách đơn giản. Trong phần ngữ pháp data binding của ASP.NET, ở giữa "<%#" và "%>", có thể viết bất cứ công thức nào nếu .NET Framework 2.0.có thể giải thích được. Ví dụ như, 1 + 1, hay"Hello" + "World", hay CatalogDataSet.Category.Rows. Nếu ghi như này và tiến hành DataBind() bằng *.aspx.cs thì công thức sẽ được đánh giá tại thời điểm (timing) đó, giá trị sẽ được đưa vào HTML. Tuy nhiên, Eval("Name", "{0}") sẽ được triển khai bằng DataBinder.Eval(Container.DataItem, "Name", "{0}") , và sẽ output ra name properties của từng data mỗi khi repeater quay lại vòng lặp (trường hợp này là CatalogDataSet.CategoryRow). Về "{0}" ở phía sau, hãy tham khảo Format() method của string class (Khi Name thì không chỉ định format đặc biệt, nhưng khi là cách viết khác với trường hợp Price thì tính maintenance sẽ giảm sút, do đó phải gắn thêm format). Để phòng tránh việc tấn công Cross Site Scripting thì HttpUtility.HtmlEncode() method sẽ thay đổi "<" thành "<". (Tấn công Cross Site Scripting là phương pháp cracking gửi scipt để tấn công toàn bộ site.) Nếu delivery source code không làm method HttpUtility.HtmlEncode() thì sẽ phát sinh vấn đề về security , và các vấn đề bồi thường do mất order nên chắc chắn các bạn không được quên.
Từ phần sau sẽ là source code của *.aspx.cs thật.
public partial class UseDataAdapterWebForm : Page
{
// Typed-DataSet.
private CatalogDataSet catalogDataSet;
// Properties để access vào Typed-DataSet. Để có thể access từ *.aspx, thì sẽ làm cách truy cập control thành protected.
protected CatalogDataSet CatalogDataSet
{
get { return catalogDataSet; }
set { catalogDataSet = value; }
}
// Event handler khi page đã được load.
protected void Page_Load(object sender, EventArgs e)
{
// Nếu không post back...
if (!IsPostBack)
{
// Sẽ tạo ra instance của Typed-DataSet.
CatalogDataSet = new CatalogDataSet();
// Lấy data từ RDBMS.
using (SqlConnection sc = new SqlConnection(
"Data Source=localhost\\SQLExpress; Initial Catalog=UsolV; Integrated Security=true;"))
{
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Category", sc))
{
sda.Fill(CatalogDataSet, "Category");
}
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Product", sc))
{
sda.Fill(CatalogDataSet, "Product");
}
}
// Thực hiện data binding.
DataBind();
}
}
}
Hãy xem source code phía trên và tưởng tượng. Có nhiều khả năng là ở chỗ khác cũng có Web Form xử lý product và category, có thể ở đó cũng viết những câu lệnh SQL tương tự. Giả sử nếu bảng Product được thay đổi để mở rộng? Sẽ phải update nhiều chỗ, do đó tính maintain sẽ bị giảm đi. Cho dù có rất ít vị trí sử dụng cùng một câu lệnh SQL và chỉ là Web Form này đi nữa, nhưng với cách tạo source code như trên, sẽ rất vất vả trong việc tìm xem cần phải thay đổi ở chỗ nào, vì vậy tất nhiên tính bảo trì sẽ bị giảm đi. Có thể source code trên khó hiểu nhưng bởi vì chắc chắn rằng xử lý Web Form sẽ còn phức tạp hơn và cần phải tìm kiếm câu lệnh SQL nằm trong xử lý phức tạp đó.
Để nâng cao tính bảo trì, sẽ tổng hợp phần trao đổi thông tin vào một class khác như Data Access Logic Component. Cụ thể sẽ thành source code như sau.
public static class CatalogDataAccessLogicCompoenent
{
public static int FillCategory(CatalogDataSet dataSet)
{
// Get category từ RDBMS.
using (SqlConnection sc = new SqlConnection("Data Source=localhost\\SQLExpress; Initial Catalog=UsolV; Integrated Security=true;"))
{
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Category", sc))
{
return sda.Fill(dataSet, "Category");
}
}
}
// Get product từ RDBMS.
public static int FillProduct(CatalogDataSet dataSet)
{
using (SqlConnection sc = new SqlConnection("Data Source=localhost\\SQLExpress; Initial Catalog=UsolV; Integrated Security=true;"))
{
using (SqlDataAdapter sda = new SqlDataAdapter("select * from Product", sc))
{
return sda.Fill(dataSet, "Product");
}
}
}
}
Web Form sử dụng Data Access Logic Component này sẽ thành source code như dưới đây. Vì phần *.aspx giống nhau nên chỉ trình bày *.aspx.cs.
public partial class UseDataAccessLogicComponentWebForm : Page
{
private CatalogDataSet catalogDataSet;
protected CatalogDataSet CatalogDataSet
{
get { return catalogDataSet; }
set { catalogDataSet = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Tạo trường hợp (instance) của Typed-DataSet.
CatalogDataSet = new CatalogDataSet();
// Gọi ra Data Access Logic Component rồi get data.
CatalogDataAccessLogicCompoenent.FillCategory(CatalogDataSet);
CatalogDataAccessLogicCompoenent.FillProduct(CatalogDataSet);
// Thực hiện data binding.
DataBind();
}
}
}
Nếu so sánh với source code trước khi làm Data Access Logic Component thành một class riêng biệt, thì source code này khá đơn giản. Giữa 2 source code , một là có thể hiểu được toàn bộ nếu như hiểu được một cách tuần tự từng class đơn giản có cung cấp các tính năng đơn giản, còn một thì buộc phải hiểu một lần tất cả những đối tượng phức tạp đó, thì source code đầu tiên dễ hiểu hơn. Con người có hạn chế về năng lực, vì vậy để có thể hiểu được những thứ phức tạp như là hệ thống máy vi tính thì việc phân chia thành những phần đơn giản như vậy sẽ rất hiệu quả.
Những kỹ thuật viên của Microsoft không những cực kỳ xuất sắc mà còn rất thân thiện. Vì vậy mà sau khi xem xét tất cả những điều mà tôi đã viết ra đến đây, họ đã xây dựng một cơ cấu hỗ trợ việc thực hiện theo như những nội dung đó trong Visual Studio 2005. (Vì là vấn đề business nên việc phải học và nâng cao service level là đương nhiên). Chức năng đó chính là TableAdapter.
Dưới đây tôi xin chiếu lại screen capture của Typed-DataSet mà tôi đã cho các bạn xem ở phần Business Entity. Nếu chú ý và nhìn kỹ từng bảng một, các bạn sẽ thấy rằng ở nửa dưới của bảng có viết Table-nameTableAdapter. Ngoài ra, những nội dung được ghi trong bảng dường như là method tương ứng với Data Access Logic Component như Fill hay GetData().

Nửa dưới là TableAdapter. Nếu click vào bảng, chọn [Add] - [Query...]ở Pop-up menu thì có thể add thêm method vào TableAdapter. Việc lập trình trở nên dễ dàng hơn bằng thao tác đơn giản như thế này là vì Visual Studio 2005 có thể tự động tạo source code giống như khi Typed-DataSet , và source code đã được sinh tự động lại tự động tạo source code trong Typed-DataSet-name.Designer.cs giống như trường hợp Typed-DataSet.
Mặc dù nói vậy, nhưng nếu việc add thêm method vào TableAdapter rắc rối hơn là viết source code thì cũng không còn ý nghĩa gì nữa. (tool không bình thường như thế này đang tồn tại rất nhiều. Ví dụ designer Web Form của Visual Studio 2005 không phải kiểu của Microsoft). Sau đây tôi xin giới thiệu cụ thể trình tự add method vào TableAdapter, và tôi sẽ cho các bạn thấy nó đơn giản hơn rất nhiều so với việc viết source code bằng tay.
Trước tiên, click phải chuột vào bảng Typed-DataSet, rồi chọn [Add] - [Query...] ở Pop-up menu.
![[Add]-[Query...]](Images/Add TableAdapter Method 1.png)
Vì sẽ bị hỏi về phương thức get data nên tại đây, hãy chọn [Use SQL Statement] nguyên như mặc định.

Vì sẽ bị hỏi là câu lệnh SQL nào nên tại đây, hãy chọn [SELECT which returns rows] nguyên như mặc định.

Tiếp đó là nhập câu lệnh SQL. Nếu tự tin về SQL, bạn có thể nhập trực tiếp ở đây cũng được, nhưng bạn có thể tạo được câu lệnh SQL một cách rất đơn giản bằng cách click vào nút [Query Builder...]. Lần này để giới thiệu với các bạn, tôi sẽ click thử vào nút [Query Builder...].

Tại QueryBuilder, bạn có thể mô tả điều kiện vào Filter như ở sơ đồ dưới đây, cho tạo lệnh WHERE, hoặc cho tạo lệnh ORDER BY bằng Sort Type và Sort Order, hoặc click phải chuột vào bảng trên, add thêm bảng rồi tạo JOIN. Lần này tôi chỉ định trực tiếp giá trị là「<= 2000」 và nếu để chỗ này là「<= @maxPrice」 thì có thể cho tìm kiếm bằng giá trị đã chỉ định từ chương trình.

Chỉ định tên và loại method sẽ sinh tự động. Ở USOL-V có quy định là chỉ chỉ định check box [Fill a DataTable] rồi lấy tên method làm FillByTableNameColumnNameĐiềuKiện. (Tôi sẽ chỉ trình bày như vậy thôi, trong tài liệu này cho phép tôi lược bớt lý do. Tôi sẽ trình bày về Software Architecture ở một trang Web mà tôi dự định sẽ public trong một ngày gần đây nên các bạn hãy chờ đến lúc đó.)

Đến đây là kết thúc xử lý add thêm method vào TableAdapter. Tôi nghĩ là các bạn đã hiểu rằng việc này còn đơn giản hơn cả việc viết source code.

Và tôi cũng xin đưa ra chứng cớ về việc đã add thêm method. Đó là ở ProductTableAdapter có một method "FillByPriceBelow2000()" đã được add.

Một bằng chứng nữa là *.aspx.cs sẽ gọi ra TableAdapter như dưới đây.
public partial class UseTableAdapterWebForm : Page
{
private CatalogDataSet catalogDataSet;
protected CatalogDataSet CatalogDataSet
{
get { return catalogDataSet; }
set { catalogDataSet = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Tạo trường hợp (instance) của Typed-DataSet.
CatalogDataSet = new CatalogDataSet();
// Gọi ra TableAdapter rồi get data.
using (CatalogDataSetTableAdapters.CategoryTableAdapter ta = new CatalogDataSetTableAdapters.CategoryTableAdapter())
{
ta.Fill(CatalogDataSet.Category);
}
using (CatalogDataSetTableAdapters.ProductTableAdapter ta = new CatalogDataSetTableAdapters.ProductTableAdapter())
{
ta.FillByProductPriceBelow2000(CatalogDataSet.Product);
}
// Thực hiện data binding.
DataBind();
}
}
}
Vì dòng của phần using tăng lên nên chi phí cũng tăng. Nhưng nếu xem xét tới sự chênh lệch giữa chi phí tạo Data Access Logic Component bằng tay và chi phí tạo TableAdapter tự động trong Visual Studio 2005thì có thể phải chi trả toàn bộ. Usol-V khuyến khích dùng TableAdapter.
Sẽ suy nghĩ về các vấn đề đã đưa ra trước, vấn đề sẽ get tất cả catagory.
Với source code lúc nãy thì sẽ get tất cả các category. Mặc dù chỉ có 2 category có computer giá dưới $2,000 nhưng vẫn get tất cả các category. (Có lẽ có khoảng 1,000,000 chủng loại.) Và điều này sẽ trở thành vấn đề không thể bỏ qua về mặt hiệu suất thực hiện.
Nếu chúng ta mô tả câu lệnh SELECT chỉ get những category có computer giá dưới $2,000 thì có thể giải quyết được vấn đề này. Chúng ta sẽ xem xét theo thứ tự dưới đây.
Trước tiên, vì chúng ta muốn get category, nên lệnh SELECT và lệnh FROM sẽ phải như dưới đây.
select Category.Identity, Name, Description from Category
Vì tôi muốn viết Product.Price <= 2000 trong lệnh WHERE nên tôi sẽ thử JOIN.
select
Category.Identity, Category.Name, Category.Description
from
Category
inner join Product on Category.Identity = Product.CategoryIdentity
Nhờ đó mà đã có thể viết được cả lệnh WHERE, nên tôi sẽ add thêm lệnh WHERE.
select
Category.Identity, Category.Name, Category.Description
from
Category
inner join Product on Category.Identity = Product.CategoryIdentity
where
Product.Price <= 2000.00
Tuy nhiên, nếu dựa vào câu lệnh SQL này để add thêm method vào TableAdapter, thì chúng ta sẽ hoàn thành một source code chưa thể chạy được hoàn thiện. Sở dĩ như vậy là bởi vì điều kiện tiền đề là phải tồn tại main key, mà DataTable nằm trong DataSet không được phép bị trùng với khóa chính đó. Vậy chúng ta hãy cùng suy nghĩ. Trường hợp có ThinkPad T61 và ThinkPad X60S giá dưới $2,000 thì ở câu lệnh SQL lúc nãy sẽ trả về kết quả như sau.
| Category.Identity | Category.Name |
|---|---|
| 1 | Lenovo ThinkPad Series |
| 1 | Lenovo ThinkPad Series |
JOIN nghĩa là kết hợp nhiều bảng để tạo ra một bảng lớn. WHERE nghĩa là chọn trong bảng dòng thỏa mãn điều kiện. SELECT nghĩa là chỉ định cột sẽ output ra, do đó mà có kết quả như ở bảng trên. Đề giải quyết vấn đề này, tôi sẽ sử dụng DISTINCT vốn là một chức năng của câu lệnh SQL. DISTINCT là chức năng tổng hợp những row có nội dung giống nhau, nhờ nó mà có thể tránh được tình trạng main key bị trùng. Dưới đây là câu lệnh SQL cụ thể.
select distinct
Category.Identity, Category.Name, Category.Description
from
Category
inner join Product on Category.Identity = Product.CategoryIdentity
where
Product.Price <= 2000.00
Nếu chúng ta sử dụng như trên, để add thêm method FillByProductPriceBelow2000() vào CategoryTableAdapter và viết lại *.aspx.cs như dưới đây thì chúng ta sẽ được đánh giá là có xem xét từ tính bảo trì đến hiệu suất thực hiện một cách toàn diện và có thể nhận được order tiếp theo.
public partial class UseTableAdapterWebForm : Page
{
private CatalogDataSet catalogDataSet;
protected CatalogDataSet CatalogDataSet
{
get { return catalogDataSet; }
set { catalogDataSet = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Tạo trường hợp (instance) của Typed-DataSet.
CatalogDataSet = new CatalogDataSet();
// Gọi ra TableAdapter rồi get data.
using (CatalogDataSetTableAdapters.CategoryTableAdapter ta = new CatalogDataSetTableAdapters.CategoryTableAdapter())
{
ta.FillByProductPriceBelow2000(CatalogDataSet.Category);
}
using (CatalogDataSetTableAdapters.ProductTableAdapter ta = new CatalogDataSetTableAdapters.ProductTableAdapter())
{
ta.FillByProductPriceBelow2000(CatalogDataSet.Product);
}
// Thực hiện data binding.
DataBind();
}
}
}
Business application sẽ được sửa lại ngay cả sau khi đã hoạt động. Tính dễ dàng chỉnh sửa lại được gọi là tính bảo trì. Và chúng ta không được phép deliver một source code có tính bảo trì thấp.
Để nâng cao tính bảo trì, điều quan trọng là phải xóa bỏ sự trùng lặp. Do đó, sẽ rất hiệu quả nếu chúng ta chia thành những nhóm class đơn giản có vai trò riêng lẻ. Nghĩa là, chúng ta không được ghi xử lý trao đổi thông tin với RDBMS vào event/handler của Windows Form và Web Form. Phải chia xử lý trao đổi thông tin với RDBMS thành một class riêng biệt như là Data Access Logic Component. Và phương thức sử dụng nguyên DataAdapter thường thấy trên tạp chí và internet vì được viết một cách tự do nên có nhiều khả năng dẫn đến việc viết xử lý trao đổi thông tin với RDBMS vào trong event/handler.
TableAdapter là chức năng sinh tự động Data Access Logic Component bằng Visual Studio 2005. TableAdapter có 2 ưu điểm là: “do tạo tự động nên chi phí cần thiết cho việc thực hiện thấp”, và “Data Access Logic Component sẽ được bắt buộc phân chia thành các class riêng biệt là TableAdapter”. Vì vừa đơn giản lại có thể nâng cao tính maintenance nên cần phải sử dụng TableAdapter trong phát triển business application.
Khi get table cha bằng điều kiện tìm kiếm của table con thì không được quên việc phải sử dụng DISTINCT để không phát sinh trùng lặp các main key.
![]() |
Copyright (C) 2007 USOL Vietnam. All right reserved. |