<Bài viết đã được chỉnh sửa lúc 2007-03-28 10:00:08dvbendoi>
Sử dụng kỹ thuật "last - in - wins" với DataAdapter
Bạn muốn tùy biến logic cập nhật DataAdapter sao cho những thay đổi được cho phép, ngay cả đối với những bản ghi đã bị thay đổi bởi những người dùng khác.
Tùy biến UpdateCommand sao cho có thể chọn các bản ghi dựa trên một trường duy nhất, thay vì kết hợp nhiều trường.
Logic cập nhật được-sinh-tự-động (do CommandBuilder cung cấp) chọn các bản ghi cho một phép cập nhật bằng cách thực hiện so trùng mỗi trường trong mệnh đề WHERE. Dưới đây là một câu lệnh cập nhật bảng Shippers (thuộc cơ sở dữ liệu Northwind):
UPDATE Shippers SET CompanyName=[NewVal]
WHERE ShipperID=[LastVal] AND CompanyName=[LastVal] AND Phone=[LastVal] ...
Cách này thường không hiệu quả vì nó làm tăng lượng dữ liệu được gửi trên mạng, và cũng gây khó khăn hơn cho bộ máy cơ sở dữ liệu so với một truy vấn chỉ so trùng một trường được-đánh-chỉ-mục. Ngoài ra, nếu có bất kỳ trường nào thay đổi từ khi dữ liệu được đổ vào DataSet, câu lệnh sẽ không thể so trùng bản ghi. Lúc này lỗi sẽ xảy ra, và thao tác cập nhật thất bại.
Có nhiều cách để giải quyết vấn đề này. Nếu muốn tăng hiệu năng DataAdapter nhưng vẫn giữ lại kiểu kiểm tra chặt concurrency, bạn nên sử dụng một timestamp (sẽ được mô tả trong mục 14.8). Nếu muốn tối ưu hiệu năng và cho phép nới lỏng concurrency với kỹ thuật “last-in-wins”, bạn có thể chọn ra các bản ghi dựa trên một trường duy nhất. Bằng cách này, ngay cả khi các trường khác trong bản ghi đã bị thay đổi, câu lệnh vẫn sẽ tìm thấy bản ghi này và cập nhật nó mà không phát sinh lỗi.
Ví dụ dưới đây sử dụng kỹ thuật “last-in-wins” để so trùng các bản ghi thuộc bảng Shippers dựa trên ShipperID:
UPDATE Shippers SET CompanyName=[NewVal]
WHERE ShipperID=[LastVal]
Các câu lệnh cập nhật tùy biến có thể được lưu trữ ở dạng các câu lệnh thủ tục (procedure command) hay các câu lệnh được-thông-số-hóa (parameterized command). Ứng dụng Console dưới đây minh họa một câu lệnh được-thông-số-hóa thực hiện cập nhật bảng Shippers:
Public Module LastInWinsConcurrency
Private ConnectionString As String = "Data Source=localhost;" & _ "Integrated Security=SSPI;Initial Catalog=Northwind"
Public Sub Main()
' Tạo các đối tượng ADO.NET.
Dim Con As New SqlConnection(ConnectionString)
Dim CmdSelect As New SqlCommand("SELECT * FROM Shippers", Con) Dim Adapter As New SqlDataAdapter(CmdSelect)
Dim Ds As New DataSet()
' Đổ dữ liệu vào DataSet.
Try
Con.Open()
Adapter.FillSchema(Ds, SchemaType.Mapped, "Shippers") Adapter.Fill(Ds, "Shippers")
Catch Err As Exception
Console.WriteLine(Err.ToString())
Finally
Con.Close()
End Try
' Định nghĩa một câu lệnh cập nhật tùy biến.
Dim UpdateSQL As String = "UPDATE Shippers SET " & _ "CompanyName=@Company, Phone=@Phone WHERE ShipperID=@ID" Dim CmdUpdate As New SqlCommand(UpdateSQL, Con)
' Ánh xạ các giá trị thông số đến các trường trong DataSet. CmdUpdate.Parameters.Add("@Company", SqlDbType.VarChar, 40, _ "CompanyName")
CmdUpdate.Parameters.Add("@Phone", SqlDbType.VarChar, 24, _ "Phone")
Dim Param As SqlParameter = CmdUpdate.Parameters.Add("@ID", _ SqlDbType.Int, Nothing, "ShipperID")
Param.SourceVersion = DataRowVersion.Original Adapter.UpdateCommand = CmdUpdate
' Áp dụng một thay đổi đối với hàng có ShipperID=1
' ("Speedy Express"), sử dụng một bản sao DataSet.
Dim DuplicateDs As DataSet = Ds.Copy()
Dim Row As DataRow = DuplicateDs.Tables(0).Rows.Find(1) Row("CompanyName") = "Deluxe Express"
Try
Con.Open()
Adapter.Update(DuplicateDs, "Shippers")
Console.WriteLine("Successfully applied change " & _ "from DuplicateDs.")
Catch Err As Exception
Console.WriteLine(Err.ToString())
Finally
Con.Close()
End Try
' Bây giờ áp dụng một thay đổi mâu thuẫn, sử dụng DataSet gốc.
' Điều này giả lập vấn đề concurrency, vì các giá trị gốc
' trong hàng không giống với thông tin trong cơ sở dữ liệu.
Row = Ds.Tables(0).Rows.Find(1)
("CompanyName") = "Super Deluxe Express"
Try
Con.Open()
' Lệnh này sẽ thất bại với logic được-sinh-tự-động. Adapter.Update(Ds, "Shippers")
Console.WriteLine("Successfully applied conflicting " & _
"change from Ds.")
Catch Err As Exception
Console.WriteLine(Err.ToString())
Finally
Con.Close()
End Try
Console.ReadLine()
End Sub
End Module
Có một hạn chế quan trọng trong kỹ thuật “last-in-wins”: những thay đổi của bạn sẽ ghi đè những thay đổi của bất kỳ người dùng nào khác đã được thực hiện trong khoảng thời gian nằm giữa lúc truy vấn và cập nhật nguồn dữ liệu. Nếu bạn cập nhật một bản ghi đã bị một người dùng khác chỉnh sửa, tất cả các giá trị nằm trong bản sao DataRow của bạn sẽ được áp dụng. Nói cách khác, chuỗi sự kiện dưới đây có thể diễn ra:
1. Người dùng A thu lấy một DataSet.
2. Người dùng B thu lấy một DataSet.
3. Người dùng A thực hiện một số thay đổi đối với một hàng.
4. Người dùng B thực hiện một thay đổi đối với một trường cũng trong hàng đó. Khi đó, tất cả các giá trị gốc từ DataRow sẽ được áp dụng, ghi đè bất kỳ thay đổi nào mà người dùng A đã thực hiện.
Để khắc phục vấn đề này, bạn cần sử dụng một kiểu concurrency “chỉ dành cho các giá trị đã-bị-thay-đổi”. Mặc dù DataSet theo dõi những hàng nào đã bị chỉnh sửa và những hàng nào không, vẫn không có cách nào để tạo các câu lệnh “chỉ dành cho các giá trị đã-bị-thay-đổi” với ADO.NET. Lựa chọn duy nhất là tự tạo cho mình một kiểu đối tượng DataAdapter, duyệt qua các hàng trong DataSet và tạo một lệnh UPDATE mới cho mỗi hàng dựa vào các giá trị đã-bị-thay-đổi của nó.
Trích từ "Các giải pháp lập trình VISUAL BASIC (tập 2)"
Bạn muốn tùy biến logic cập nhật DataAdapter sao cho những thay đổi được cho phép, ngay cả đối với những bản ghi đã bị thay đổi bởi những người dùng khác.
Tùy biến UpdateCommand sao cho có thể chọn các bản ghi dựa trên một trường duy nhất, thay vì kết hợp nhiều trường.
Logic cập nhật được-sinh-tự-động (do CommandBuilder cung cấp) chọn các bản ghi cho một phép cập nhật bằng cách thực hiện so trùng mỗi trường trong mệnh đề WHERE. Dưới đây là một câu lệnh cập nhật bảng Shippers (thuộc cơ sở dữ liệu Northwind):
UPDATE Shippers SET CompanyName=[NewVal]
WHERE ShipperID=[LastVal] AND CompanyName=[LastVal] AND Phone=[LastVal] ...
Cách này thường không hiệu quả vì nó làm tăng lượng dữ liệu được gửi trên mạng, và cũng gây khó khăn hơn cho bộ máy cơ sở dữ liệu so với một truy vấn chỉ so trùng một trường được-đánh-chỉ-mục. Ngoài ra, nếu có bất kỳ trường nào thay đổi từ khi dữ liệu được đổ vào DataSet, câu lệnh sẽ không thể so trùng bản ghi. Lúc này lỗi sẽ xảy ra, và thao tác cập nhật thất bại.
Có nhiều cách để giải quyết vấn đề này. Nếu muốn tăng hiệu năng DataAdapter nhưng vẫn giữ lại kiểu kiểm tra chặt concurrency, bạn nên sử dụng một timestamp (sẽ được mô tả trong mục 14.8). Nếu muốn tối ưu hiệu năng và cho phép nới lỏng concurrency với kỹ thuật “last-in-wins”, bạn có thể chọn ra các bản ghi dựa trên một trường duy nhất. Bằng cách này, ngay cả khi các trường khác trong bản ghi đã bị thay đổi, câu lệnh vẫn sẽ tìm thấy bản ghi này và cập nhật nó mà không phát sinh lỗi.
Ví dụ dưới đây sử dụng kỹ thuật “last-in-wins” để so trùng các bản ghi thuộc bảng Shippers dựa trên ShipperID:
UPDATE Shippers SET CompanyName=[NewVal]
WHERE ShipperID=[LastVal]
Các câu lệnh cập nhật tùy biến có thể được lưu trữ ở dạng các câu lệnh thủ tục (procedure command) hay các câu lệnh được-thông-số-hóa (parameterized command). Ứng dụng Console dưới đây minh họa một câu lệnh được-thông-số-hóa thực hiện cập nhật bảng Shippers:
Public Module LastInWinsConcurrency
Private ConnectionString As String = "Data Source=localhost;" & _ "Integrated Security=SSPI;Initial Catalog=Northwind"
Public Sub Main()
' Tạo các đối tượng ADO.NET.
Dim Con As New SqlConnection(ConnectionString)
Dim CmdSelect As New SqlCommand("SELECT * FROM Shippers", Con) Dim Adapter As New SqlDataAdapter(CmdSelect)
Dim Ds As New DataSet()
' Đổ dữ liệu vào DataSet.
Try
Con.Open()
Adapter.FillSchema(Ds, SchemaType.Mapped, "Shippers") Adapter.Fill(Ds, "Shippers")
Catch Err As Exception
Console.WriteLine(Err.ToString())
Finally
Con.Close()
End Try
' Định nghĩa một câu lệnh cập nhật tùy biến.
Dim UpdateSQL As String = "UPDATE Shippers SET " & _ "CompanyName=@Company, Phone=@Phone WHERE ShipperID=@ID" Dim CmdUpdate As New SqlCommand(UpdateSQL, Con)
' Ánh xạ các giá trị thông số đến các trường trong DataSet. CmdUpdate.Parameters.Add("@Company", SqlDbType.VarChar, 40, _ "CompanyName")
CmdUpdate.Parameters.Add("@Phone", SqlDbType.VarChar, 24, _ "Phone")
Dim Param As SqlParameter = CmdUpdate.Parameters.Add("@ID", _ SqlDbType.Int, Nothing, "ShipperID")
Param.SourceVersion = DataRowVersion.Original Adapter.UpdateCommand = CmdUpdate
' Áp dụng một thay đổi đối với hàng có ShipperID=1
' ("Speedy Express"), sử dụng một bản sao DataSet.
Dim DuplicateDs As DataSet = Ds.Copy()
Dim Row As DataRow = DuplicateDs.Tables(0).Rows.Find(1) Row("CompanyName") = "Deluxe Express"
Try
Con.Open()
Adapter.Update(DuplicateDs, "Shippers")
Console.WriteLine("Successfully applied change " & _ "from DuplicateDs.")
Catch Err As Exception
Console.WriteLine(Err.ToString())
Finally
Con.Close()
End Try
' Bây giờ áp dụng một thay đổi mâu thuẫn, sử dụng DataSet gốc.
' Điều này giả lập vấn đề concurrency, vì các giá trị gốc
' trong hàng không giống với thông tin trong cơ sở dữ liệu.
Row = Ds.Tables(0).Rows.Find(1)
("CompanyName") = "Super Deluxe Express"
Try
Con.Open()
' Lệnh này sẽ thất bại với logic được-sinh-tự-động. Adapter.Update(Ds, "Shippers")
Console.WriteLine("Successfully applied conflicting " & _
"change from Ds.")
Catch Err As Exception
Console.WriteLine(Err.ToString())
Finally
Con.Close()
End Try
Console.ReadLine()
End Sub
End Module
Có một hạn chế quan trọng trong kỹ thuật “last-in-wins”: những thay đổi của bạn sẽ ghi đè những thay đổi của bất kỳ người dùng nào khác đã được thực hiện trong khoảng thời gian nằm giữa lúc truy vấn và cập nhật nguồn dữ liệu. Nếu bạn cập nhật một bản ghi đã bị một người dùng khác chỉnh sửa, tất cả các giá trị nằm trong bản sao DataRow của bạn sẽ được áp dụng. Nói cách khác, chuỗi sự kiện dưới đây có thể diễn ra:
1. Người dùng A thu lấy một DataSet.
2. Người dùng B thu lấy một DataSet.
3. Người dùng A thực hiện một số thay đổi đối với một hàng.
4. Người dùng B thực hiện một thay đổi đối với một trường cũng trong hàng đó. Khi đó, tất cả các giá trị gốc từ DataRow sẽ được áp dụng, ghi đè bất kỳ thay đổi nào mà người dùng A đã thực hiện.
Để khắc phục vấn đề này, bạn cần sử dụng một kiểu concurrency “chỉ dành cho các giá trị đã-bị-thay-đổi”. Mặc dù DataSet theo dõi những hàng nào đã bị chỉnh sửa và những hàng nào không, vẫn không có cách nào để tạo các câu lệnh “chỉ dành cho các giá trị đã-bị-thay-đổi” với ADO.NET. Lựa chọn duy nhất là tự tạo cho mình một kiểu đối tượng DataAdapter, duyệt qua các hàng trong DataSet và tạo một lệnh UPDATE mới cho mỗi hàng dựa vào các giá trị đã-bị-thay-đổi của nó.
Trích từ "Các giải pháp lập trình VISUAL BASIC (tập 2)"