1. Lập trình ứng dụng quản lý 2
Tìm kiếm & phân trang với LINQ to SQL
Ngô Ngọc Đăng Khoa
2011, April 26
2. 1 LTUDQL2 – TUT05
1 Mục tiêu & Yêu cầu
1.1 Mục tiêu
Nắm được phương pháp xây dựng chức năng tìm kiếm & phân trang
trong ứng dụng LINQ to SQL với VB.NET
1.2 Yêu cầu
Sinh viên đã hoàn thành tốt 2 tutorial LINQ trước
Đọc kỹ & thực hiện các bước theo tutorial
2 Bài tập áp dụng
2.1 Yêu cầu
Sử dụng CSDL Northwind, xây dựng chức năng tìm kiếm các sản phẩm đang
có trong CSDL theo các tiêu chí sau:
Tên sản phẩm
Danh mục
Nhà cung cấp
Giá
Kết quả tìm kiếm phải được phân trang (1 trang 10 kết quả) & có dàn nút
điều hướng trang đầy đủ.
3. 2 LTUDQL2 – TUT05
2.2 Hướng dẫn
(Sinh viên sử dụng script CSDL Northwind ở tuần 7)
Bước 1: Add LINQ to SQL class ứng với CSDL Northwind vào project
Bước 2: thiết kế giao diện frmMain như hình dưới
Bước 3: khai báo 1 context toàn cục & đổ dữ liệu vào 2 comboBox
Dim ctx As New NorthwindDataContext
Private Sub frmMain_Load(...) Handles MyBase.Load
Dim listCat As List(Of Category) = ctx.Categories.ToList()
listCat.Insert(0, New Category With {.CategoryID = -1, .CategoryName = "ALL"})
cboCat.DataSource = listCat
cboCat.DisplayMember = "CategoryName"
cboCat.ValueMember = "CategoryID"
Dim listSupp As List(Of Supplier) = ctx.Suppliers.ToList()
listSupp.Insert(0, New Supplier With {.SupplierID = -1, .CompanyName = "ALL"})
cboSupp.DataSource = listSupp
cboSupp.DisplayMember = "CompanyName"
cboSupp.ValueMember = "SupplierID"
End Sub
4. 3 LTUDQL2 – TUT05
Bước 4: xây dựng hàm tạo câu truy vấn dựa vào dữ liệu nhập trên các controls
Function BuildSearchQuery() As IQueryable(Of Product)
Dim query As IQueryable(Of Product) = ctx.Products
If txtProductName.Text.Length > 0 Then
query = query.Where( _
Function(p) p.ProductName.Contains(txtProductName.Text) _
)
End If
Dim catId As Integer = cboCat.SelectedValue
If catId > -1 Then
query = query.Where(Function(p) p.CategoryID = catId)
End If
Dim suppId As Integer = cboSupp.SelectedValue
If suppId > -1 Then
query = query.Where(Function(p) p.SupplierID = suppId)
End If
Dim priceF As Integer = txtPriceF.Value
If priceF > 0 Then
query = query.Where(Function(p) p.UnitPrice >= priceF)
End If
Dim priceT As Integer = txtPriceT.Value
If priceT > 0 Then
query = query.Where(Function(p) p.UnitPrice <= priceT)
End If
Return query
End Function
Bước 5: xây dựng hàm thực hiện chức năng tìm kiếm
Private Sub btnSearch_Click(...) Handles btnSearch.Click
Dim query As IQueryable(Of Product) = BuildSearchQuery()
grid.DataSource = query
End Sub
Bước 6: bổ sung các tham số dùng cho việc chuyển trang (các biến toàn cục)
Dim ctx As New NorthwindDataContext
Const PAGE_SIZE As Integer = 10
Dim curPage As Integer
Dim numberOfPages As Integer
5. 4 LTUDQL2 – TUT05
Bước 7: xây dựng hàm điều khiển trạng thái của các nút điều hướng trang
Sub SetNavigationState()
btnNext.Enabled = (curPage < numberOfPages)
btnLast.Enabled = (curPage < numberOfPages)
btnPrev.Enabled = (curPage > 1)
btnFirst.Enabled = (curPage > 1)
End Sub
Bước 8: bổ sung lời gọi hàm vào hàm frmMain_Load
Private Sub frmMain_Load(...) Handles MyBase.Load
Dim listCat As List(Of Category) = ctx.Categories.ToList()
listCat.Insert(0, New Category With {.CategoryID = -1, .CategoryName = "ALL"})
cboCat.DataSource = listCat
cboCat.DisplayMember = "CategoryName"
cboCat.ValueMember = "CategoryID"
Dim listSupp As List(Of Supplier) = ctx.Suppliers.ToList()
listSupp.Insert(0, New Supplier With {.SupplierID = -1, .CompanyName = "ALL"})
cboSupp.DataSource = listSupp
cboSupp.DisplayMember = "CompanyName"
cboSupp.ValueMember = "SupplierID"
lbPaging.Text = "0/0"
SetNavigationState()
End Sub
Bước 9: sửa hàm btnSearch_Click để ứng dụng lấy dữ liệu đúng trang cần thiết
& tính toán các tham số cho việc phân trang
Private Sub btnSearch_Click(...) Handles btnSearch.Click
Dim query As IQueryable(Of Product) = BuildSearchQuery()
numberOfPages = Math.Ceiling(query.Count() / PAGE_SIZE)
curPage = 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = query.Skip((curPage - 1) * PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
6. 5 LTUDQL2 – TUT05
Bước 10: xây dựng các hàm điều hướng trang, cuối mỗi hàm đều cần gọi lại hàm
SetNavigationState nhằm chỉnh lại tình trạng của dàn nút điều hướng
Private Sub btnNext_Click(...) Handles btnNext.Click
curPage += 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
Private Sub btnPrev_Click(...) Handles btnPrev.Click
curPage -= 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
Private Sub btnLast_Click(...) Handles btnLast.Click
curPage = numberOfPages
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
Private Sub btnFirst_Click(...) Handles btnFirst.Click
curPage = 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
Nhận xét: trong các hàm điều hướng, ta nhận thấy đều có lời gọi hàm
BuildSearchQuery nhằm tạo lại câu truy vấn, điều đó dẫn đến hệ quả người
dùng có thể thay đổi điều kiện tìm kiếm & bấm vào dàn nút điều hướng để xem
kết quả truy vấn mới, việc này trong một số trường hợp sẽ dẫn đến lỗi (vd: kết quả
truy vấn cũ có 4 trang & người dùng đang xem trang 3, người dùng sau đó thay đổi điều
kiện truy vấn và nhấn Next. Tuy nhiên, kết quả truy vấn mới chỉ có 2 trang & gây ra lỗi).
Để giải quyết tình trạng trên, ta cần bổ sung 1 số thành phần nhằm đảm bảo
người dùng chỉ có thể chuyển trang sau khi bấm Search, nếu người dùng thay
đổi điều kiện tìm kiếm thì họ phải bấm Search & xem kết quả từ trang 1.
7. 6 LTUDQL2 – TUT05
Bước 11: bổ sung cờ ngăn chặn việc chuyển trang khi chưa bấm Search (biến
toàn cục)
''' <summary>
''' Cờ dùng để ngăn các thao tác chuyển trang
''' </summary>
''' <remarks></remarks>
Dim isDirty As Boolean
Bước 12: thực hiện bật cờ khi người dùng thay đổi các điều kiện tìm kiếm
Private Sub cboSupp_SelectedIndexChanged(...) ...
isDirty = True
End Sub
Private Sub cboCat_SelectedIndexChanged(...) ...
isDirty = True
End Sub
Private Sub txtProductName_TextChanged(...) ...
isDirty = True
End Sub
Private Sub txtPriceF_ValueChanged(...) ...
isDirty = True
End Sub
Private Sub txtPriceT_ValueChanged(...) ...
isDirty = True
End Sub
Bước 13: xây dựng hàm thông báo lỗi khi chuyển trang mà chưa bấm Search
Sub RaisePageError()
MessageBox.Show("Cần thực hiện Search trước khi chuyển trang.", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Sub
8. 7 LTUDQL2 – TUT05
Bước 14: thực hiện ngăn thao tác chuyển trang từ đầu
Private Sub frmMain_Load(...) Handles MyBase.Load
Dim listCat As List(Of Category) = ctx.Categories.ToList()
listCat.Insert(0, New Category With {.CategoryID = -1, .CategoryName = "ALL"})
cboCat.DataSource = listCat
cboCat.DisplayMember = "CategoryName"
cboCat.ValueMember = "CategoryID"
Dim listSupp As List(Of Supplier) = ctx.Suppliers.ToList()
listSupp.Insert(0, New Supplier With {.SupplierID = -1, .CompanyName = "ALL"})
cboSupp.DataSource = listSupp
cboSupp.DisplayMember = "CompanyName"
cboSupp.ValueMember = "SupplierID"
isDirty = True 'ngăn ko cho thực hiện các thao tác chuyển trang
lbPaging.Text = "0/0"
SetNavigationState()
End Sub
Bước 15: bắt đầu đồng ý cho chuyển trang khi người dùng đã nhấn Search
Private Sub btnSearch_Click(...) Handles btnSearch.Click
Dim query As IQueryable(Of Product) = BuildSearchQuery()
numberOfPages = Math.Ceiling(query.Count() / PAGE_SIZE)
curPage = 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = query.Skip((curPage - 1) * PAGE_SIZE).Take(PAGE_SIZE)
isDirty = False 'bắt đầu cho phép chuyển trang
SetNavigationState()
End Sub
Bước 16: ngăn chặn các thao tác chuyển trang trái phép trong các hàm điều
hướng trang
Private Sub btnNext_Click(...) Handles btnNext.Click
If isDirty Then
RaisePageError()
Return
End If
curPage += 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
9. 8 LTUDQL2 – TUT05
Private Sub btnPrev_Click(...) Handles btnPrev.Click
If isDirty Then
RaisePageError()
Return
End If
curPage -= 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
Private Sub btnLast_Click(...) Handles btnLast.Click
If isDirty Then
RaisePageError()
Return
End If
curPage = numberOfPages
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
Private Sub btnFirst_Click(...) Handles btnFirst.Click
If isDirty Then
RaisePageError()
Return
End If
curPage = 1
lbPaging.Text = String.Format("{0}/{1}", curPage, numberOfPages)
grid.DataSource = BuildSearchQuery().Skip((curPage - 1) *
PAGE_SIZE).Take(PAGE_SIZE)
SetNavigationState()
End Sub
10. 9 LTUDQL2 – TUT05
Bước 17: xây dựng hàm Reset form
Private Sub btnClear_Click(...) Handles btnClear.Click
txtProductName.Clear()
txtPriceF.Value = 0
txtPriceT.Value = 0
cboCat.SelectedIndex = 0
cboSupp.SelectedIndex = 0
grid.DataSource = Nothing
isDirty = True
curPage = 0
numberOfPages = 0
lbPaging.Text = "0/0"
End Sub
3 Phụ lục (sửa câu truy vấn phép chia trong tutorial trước)
Đề: cho biết danh sách các sinh viên đăng ký tất cả các môn học
Dim ctx As New QLSVDataContext
Dim query = ctx.SinhViens.Where( _
Function(sv) sv.DangKyHocPhans.Distinct().Count() = ctx.MonHocs.Count() _
)
grid.DataSource = query