Dùng biểu thức LINQ tùy biến với <asp:LinqDatasource> (LINQ to SQL phần 9)


Vài tuần trước tôi bắt đầu viết loạt bài về LINQ to SQL. LINQ to SQL là một bộ khung (framework) có sẵn cho O/RM (object relational mapping) trong .NET 3.5, nó cho phép bạn dễ dàng mô hình hóa các CSDL quan hệ dùng các lớp .NET. Bạn có thể dùng các biểu thức LINQ để truy vấn CSDL, cũng như có thể cập nhật/thêm/xóa dữ liệu từ đó.

Dưới đây là 8 phần đầu tiên của loạt bài này:

Trong phần 5 của loạt bài này tôi đã giới thiệu control <asp:LinqDataSource> mới trong .NET 3.5 và nói về cách dùng nó để gắn nối các control ASP.NET dễ dàng vào các mô hình dữ liệu LINQ to SQL. Tôi cũng đã trình bày một chút về cách dùng chúng trong một bài viết sau đó khi nói về control <asp:ListView>.

Trong cả hai bài viết trên, các câu truy vấn được thực hiện đều tương đối dễ hiểu (mệnh đề Where làm việc chỉ với một bảng dữ liệu). Trong bài viết hôm nay tôi sẽ biểu diễn cách tận dụng khả năng xây dựng các câu truy vấn nhanh chóng với LINQ dùng LinqDataSource, và cách bạn có thể dùng bất kỳ biểu thức LINQ nào để thực hiện truy vấn với nó.

Tóm tắt: dùng <asp:LinqDataSource> với một mệnh đề where được khai báo

Trong 2 bài viết đó tôi đã biểu diễn cách bạn có thể dùng các bộ lọc có sẵn của LinqDataSource để khai báo nhanh một bộ lọc trên một mô hình dữ liêu LINQ to SQL.

Ví dụ, cho là bạn đã tạo ra một mô hình dữ liệu LINQ to SQL của CSDL Northwind (cách dùng đã được nói đến trong phần 2 cuẩ loạt bài này), chúng ta có có thể khai báo một control <asp:LinqDataSource> trên trang với một mệnh để <where> mà nó chỉ trả về các sản phẩm thuộc một chủng loại nào đó (được chỉ ra qua tham số “categoryid” của chuỗi query string):

Chúng ta có thể trỏ một control &lt;asp:gridview&gt; đến datasource đã tạo và cho phép phân trang, chỉnh sửa và sắp xếp:

Khi chạy trang trên, chúng ta sẽ có một GridView với khả năng tự động sắp xếp, phân trang cũng như chỉnh sửa dữ liệu dữ trên dữ liệu có trong mô hình dữ liệu của chúng ta:

Dùng cách khai báo các tham số cho where giống như trên có thể làm việc tốt trong hầu hết trường hợp. Nhưng sẽ thế nào nếu bạn muốn câu lệnh lọc phức tạp hơn? Ví dụ, sẽ thế nào nếu chúng ta chỉ muốn hiển thị các sản phẩm được tạo bới các nhà cung cấp dựa trên một tập động các quốc gia?

Dùng các sự kiện Selecting với <asp:LinqDataSource>

Để xử lý các trường hợp trên, bạn có thể tạo các hàm xử lý cho các sự kiện “Selecting” thuộc control &lt;asp:LinqDataSourc&gt;. Bên trong các hàm xử lý sự kiện này, bạn có thể viết bất kỳ đoạn lệnh nào bạn muốn để lấy về tập kết quả. Bạn có thể làm được điều này với một câu truy vấn LINQ, gọi một thủ tục SPROC hay dùng một biểu thức SQL tùy biến. Một khi đã lấy dữ liệu về, những gì cần làm là gán nó cho thuộc tính “Result” của đối tượng LinqDataSourceSelectEventArgs. Control &lt;asp:LinqDataSourc&gt; khi đói sẽ dùng tập kết quả do bạn trả về để làm việc.

Ví dụ, dưới đây là một câu truy vấn LINQ to SQL để lấy về chỉ các sản phẩm từ các nhà cung cấp thuộc các nước được chọn:

VB:

C#:

Ghi chú: Bạn không cần viết câu truy vấn ngay bên trong hàm xử lý sự kiện. Một cách tiếp cận sáng sủa hơn là đưa các câu lệnh truy vấn vào trong các hàm trợ giúp, và sau đó gọi lại chúng từ các hàm xử lý sự kiện. Tôi đã dùng cách tiếp cận này trong phần đầu của bài 8 (dùng hàm trợ giúp GetProductsByCategor).

Bây giờ, bằng cách dùng hàm xử lý Selecting, mỗi khi chạy bạn sẽ chỉ thấy các sản phẩm được cung cấp bởi các nhà cung cấp đến từ các quốc gia mà chúng ta đã cho trước.

Một trong những điều thật sự thú vị là các chức năng phân trang và sắp xếp vẫn làm việc với GridView của chúng ta – dù rằng chúng ta đã chuyển sang dùng sự kiện Selecting. Và quan trọng là việc phân trang cũng như sắp xếp này được thực hiện bên trong CSDL – có nghĩa là chúng ta chỉ lấy về 10 sản phẩm từ CSDL mà chúng ta cần để hiển thị cho trang hiện tại trên GridView, điều này giúp việc thực thi hiệu quả hơn rất nhiều.

Bạn có lẽ sẽ tự hỏi – làm sao nó có thẻ hỗ trợ việc sắp xếp và phân trang hiệu quả như vậy ngay cả khi ta dùng sự kiện Selecting? Lý do là vì câu truy vấn LINQ sẽ không được thực thi tới chừng nào bạn còn chưa lấy kết quả trả về của nó (deferred execution model). Ưu điểm của mô hình này là nó cho phép bạn dễ dàng soạn câu truy vấn trước khi thực thi nó, cũng như dễ dàng đưa thêm các tính năng “add-on” và. Bạn có thể tìm hiểu kỹ hơn trong phần 3 của loạt bài này.

Trong hàm xử lý sự kiện “Selecting” ở trên chúng ta khai báo câu truy vấn LINQ chúng ta muốn thực thi và sau đó gán nó vào thuộc tính e.Query. Mặc dù vậy, câu lênh LINQ không được thực thi vì chúng ta không lấy kêt quả của nó (bằng cách dùng những hàm như ToArray() hay ToList()). LINQDataSource sau đó sẽ có thể thêm mệnh đề order by, và nối thêm các hàm mở rộng Take() và Skip(), nhờ vậy mà tập kết quả sẽ được phân trang và sắp xếp. Chỉ khi đó LINQDataSource mới thực hiện câu lệnh LINQ và lấy dữ liệu về, và LINQ to SQL sẽ đảm bảo rằng việc sắp xếp và phân trang này được thực hiện bên trong CSDL – và chỉ có đúng 10 dòng được trả về.

Chú ý dưới đây chúng ta vẫn dùng GridView để chỉnh sửa và xóa dữ liệu, ngay cả khi dùng sự kiện “Selecting” của LinqDataSource:

Khả năng hỗ trợ việc xóa/sửa dữ liệu sẽ còn làm việc chừng nào thuộc tính Query của sự kiện Selecting còn được gán một tập các thực thể cùng loại (ví du: một dãy các đối tượng kiểu Product, Supplier, Category, Order…). LINQDataSource khi đó sẽ có thể tự động xử lý các trường hợp UI control thực hiện việc cập nhật đối với nó.

Để học thêm về cách cập nhật trong LINQ to SQL, xin đọc lại bài 4 của loạt bài này. Và sau đó đọc tiếp bài 5 để xem cách cập nhật với LINQDataSource.

Thực hiện các phép chiếu khi truy vấn với sự kiện Selecting

Một trong những điểm mạnh của LINQ là khả năng trả về các “dạng” dữ liệu tùy biến, hay còn gọi là phép chiếu dữ liệu. Đó là khả năng mà bạn chỉ trả về một tập con các giá trị của thực thể (một số cột nào đó mà thôi), hay trả về các giá trị được tính toán tự động bằng các biểu thức do bạn định nghĩa. Bạn có thể tìm hiểu thêm cách LINQ thực hiện các phép chiếu này trong phần 3 của loạt bài này.

Ví dụ, bạn có thể sửa lại sự kiện hàm xử lý sự kiện Selecting để đưa thông tin vào cho GridView một tập tùy biến các giá trị của Product. Trong grid này, ta sẽ chỉ hiển thị ProductID, ProductName, Product UnitPrice, số lệnh đặt hàng trên sản phẩm này (Number of Orders), và doanh thu của sản phẩm (Revenue). Chúng ta có thể tính toán động 2 giá trị cuối dùng một biểu thức LINQ như dưới đây:

VB:

C#:

Ghi chú: hàm Sum được dùng để tính toán Revenue ở trên là một ví dụ về “Phương thức mở rộng” (Extension Method). Tham số được truyền cho hàm này là một ví dụ về biểu thức Lambda. Kiểu trả về được tạo bởi biểu thức LINQ là một kiểu vô danh (anonymous type) và nó được hình thành từ biểu thức truy vấn. Extension Methods, Lambda Expressions, và Anonymous Types là các đặc tính mới của VB và C# trong VS 2008.

Kết quả của biểu thức LINQ trên khi gắn nối vào GridView sẽ tương tự như sau:

Ghi chú rằng việc phân trang và sắp xếp sẽ vẫn làm việc bình thường với GridView của chúng ta – dù rằng chúng ta đã chuyển sang dùng câu lệnh LINQ tùy biến.

Dù vậy, vẫn có một tính năng sẽ không làm việc khi dùng phép chiếu dữ liệu, đó là việc hỗ trợ cập nhật dữ liệu ngay trong GridView. Đó là vì LINQDataSource không biết cách nào để cập nhật dữ liệu một cách an toàn. Nếu chúng ta muốn thêm khả năng cập nhật vào cho GridView để hỗ trợ các kiểu trả về tùy biến như vậy, chúng ta hoặc sẽ phải chuyển sang dùng một control ObjectDataSource (ta phải cung cấp thêm phương thức Update để xử lý việc cập nhật), hoặc phải cung cấp thêm một trang để người dùng cập nhật – và hiển thị môt DetailsView hay FormViewgắn nối và thực thể Product để chỉnh sửa.

Tổng kết

Bạn có thể dễ dàng thực hiện các thao tác truy vấn thường dùng với mô hình dữ liệu LINQ to SQL dùng khả năng khai báo các bọ lọc của LINQDataSource.

Để thực hiện biểu thức lọc phức tạp hơn, bạn có thể tận dụng ưu điểm của sự kiên Selecting có trong LINQDataSource. Điều này cho phép bạn thực hiện bất kỳ logic nào bạn muốn để  lấy về các dòng dữ liệu phù hợp. Bạn có thể nhiều cách để lấy dữ liệu này, chẳng hạn dùng Query Expressions, gọi Stored Procedures, hay thực hiện một câu truy vấn tùy biến.

Hope this helps,

Scott

Vài tuần trước tôi bắt đầu viết loạt bài về LINQ to SQL. LINQ to SQL là một bộ khung (framework) có sẵn cho O/RM (object relational mapping) trong .NET 3.5, nó cho phép bạn dễ dàng mô hình hóa các CSDL quan hệ dùng các lớp .NET. Bạn có thể dùng các biểu thức LINQ để truy vấn CSDL, cũng như có thể cập nhật/thêm/xóa dữ liệu từ đó.

Dưới đây là 7 phần đầu tiên của loạt bài này:

Trong phần 5 của loạt bài này tôi đã giới thiệu control <asp:LinqDataSource> mới trong .NET 3.5 và nói về cách dùng nó để gắn nối các control ASP.NET dễ dàng vào các mô hình dữ liệu LINQ to SQL. Tôi cũng đã trình bày một chút về cách dùng chúng trong một bài viết sau đó khi nói về control <asp:ListView>.

In both of these articles the queries I performed were relatively straight-forward (the where clause worked against a single table of data).  In today’s blog post I’ll demonstrate how to use the full query expressiveness of LINQ with the LinqDataSource control, and show how you can use any LINQ to SQL query expression with it.

Trong cả hai bài viết trên, các câu truy vấn được thực hiện đều tương đối dễ hiểu (mệnh đề Where làm việc chỉ với một bảng dữ liệu). Trong bài viết hôm nay tôi sẽ biểu diễn cách tận dụng khả năng xây dựng các câu truy vấn nhanh chóng với LINQ dùng LinqDataSource, và cách bạn có thể dùng bất kỳ biểu thức LINQ nào để thực hiện truy vấn với nó.

Tóm tắt: dùng <asp:LinqDataSource> với một mệnh đề where được khai báo

Trong 2 bài viết đó tôi đã biểu diễn cách bạn có thể dùng các bộ lọc có sẵn của LinqDataSource để khai báo nhanh một bộ lọc trên một mô hình dữ liêu LINQ to SQL.

Ví dụ, cho là bạn đã tạo ra một mô hình dữ liệu LINQ to SQL của CSDL Northwind (cách dùng đã được nói đến trong phần 2 cuẩ loạt bài này), chúng ta có có thể khai báo một control &lt;asp:LinqDataSource&gt; trên trang với một mệnh để &lt;where&gt; mà nó chỉ trả về các sản phẩm thuộc một chủng loại nào đó (được chỉ ra qua tham số “categoryid” của chuỗi query string):

Chúng ta có thể trỏ một control &lt;asp:gridview&gt; đến datasource đã tạo và cho phép phân trang, chỉnh sửa và sắp xếp:

Khi chạy trang trên, chúng ta sẽ có một GridView với khả năng tự động sắp xếp, phân trang cũng như chỉnh sửa dữ liệu dữ trên dữ liệu có trong mô hình dữ liệu của chúng ta:

Dùng cách khai báo các tham số cho where giống như trên có thể làm việc tốt trong hầu hết trường hợp. Nhưng sẽ thế nào nếu bạn muốn câu lệnh lọc phức tạp hơn? Ví dụ, sẽ thế nào nếu chúng ta chỉ muốn hiển thị các sản phẩm được tạo bới các nhà cung cấp dựa trên một tập động các quốc gia?

Dùng các sự kiện Selecting với &lt;asp:LinqDataSource&gt;

Để xử lý các trường hợp trên, bạn có thể tạo các hàm xử lý cho các sự kiện “Selecting” thuộc control &lt;asp:LinqDataSourc&gt;. Bên trong các hàm xử lý sự kiện này, bạn có thể viết bất kỳ đoạn lệnh nào bạn muốn để lấy về tập kết quả. Bạn có thể làm được điều này với một câu truy vấn LINQ, gọi một thủ tục SPROC hay dùng một biểu thức SQL tùy biến. Một khi đã lấy dữ liệu về, những gì cần làm là gán nó cho thuộc tính “Result” của đối tượng LinqDataSourceSelectEventArgs. Control &lt;asp:LinqDataSourc&gt; khi đói sẽ dùng tập kết quả do bạn trả về để làm việc.

For example, below is a LINQ to SQL query expression that retrieves only products from suppliers based in a specific set of countries:

VB:

C#:

Note: you do not need to write your query expression in-line within the event handler.  A cleaner approach would be to encapsulate it within a helper method that you just call from the event handler.  I show how to create one of these helper methods in the beginning of my Part 8 blog post (using a GetProductsByCategory helper method).

Now when we run our page using the custom Selecting event handler, we’ll only see those products whose suppliers are located in our array of countries:

One of the really cool things to notice above is that paging and sorting still work with our GridView – even though we are using a custom Selecting event to retrieve the data.  This paging and sorting logic happens in the database – which means we are only pulling back the 10 products from the database that we need to display for the current page index in the GridView (making it super efficient).

You might ask yourself – how is it possible that we get efficient paging and sorting support even when using a custom selecting event?  The reason is because LINQ uses a deferred execution model – which means that the query doesn’t actually execute until you try and iterate over the results.  One of the benefits of this deferred execution model is that it enables you to nicely compose queries out of other queries, and effectively “add-on” behavior to them.  You can learn more about this in my LINQ to SQL Part 3 blog post.

In our “Selecting” event handler above we are declaring a custom LINQ query we want to execute and are then assigning it to the “e.Result” property.  We haven’t actually executed it yet though (since we didn’t try and iterate through the results or call ToArray() or ToList() on it).  The LINQDataSource is therefore able to automatically append on a Skip() and Take() operator to the query, as well as apply an “orderby” expression to it — all of these values being automatically calculated from the page index and sort preference of the GridView.  Only then does the LINQDataSource execute the LINQ expression and retrieve the data.  LINQ to SQL then takes care of making sure that the sort and page logic is handled in the database – and that only the 10 product rows required are returned from it.

Notice below how we can also still use the GridView to edit and delete data, even when using a custom LinqDataSource “Selecting” event:

This editing/deleting support will work as long as our Selecting event assigns a Result query whose result sequence is of regular entity objects (for example: a sequence of type Product, Supplier, Category, Order, etc).  The LINQDataSource can then automatically handle cases where UI controls perform updates against them.

To learn more about how updates work with LINQ to SQL, please read Part 4 of this series.  Then read Part 5 of the series to see Updates in action with the LinqDataSource.

Performing Custom Query Projections with the Selecting Event

One of the powerful features of LINQ is its ability to custom “shape” or “project” data.  You can do this in a LINQ to SQL expression to indicate that you want to retrieve only a subset of values from an entity, and/or to dynamically compute new values on the fly using custom expressions that you define.  You can learn more about how these LINQ query projection/shaping capabilities in Part 3 of this series.

For example, we could modify our “Selecting” event handler to populate a GridView to display a custom set of Product information.  In this Grid we’ll want to display the ProductID, Product Name, Product UnitPrice, the Number of Orders made for this Product, and the total Revenue collected from orders placed for the Product.  We can dynamically compute these last two values using a LINQ expression like below:

VB:

C#:

Note: The Sum method used in the Revenue statement above is an example of an Extension Method.  The function it takes is an example of a Lambda expression.  The resulting type created by the LINQ query expression is an anonymous type – since its shape is inferred from the query expression.  Extension Methods, Lambda Expressions, and Anonymous Types are all new language features of VB and C# in VS 2008.

The result of our custom LINQ expression when bound to the GridView will be UI like below:

Note that paging and sorting still work above with our GridView – even though we are using a custom LINQ shape/projection for the data.

One feature that will not work with custom shapes/projections, though, is inline editing support.  This is because we are doing a custom projection in our Selecting event, and so the LinqDataSource has no way to safely know how to update an underlying entity object.  If we want to add editing support to the GridView with a custom shaped type, we’d want to either move to using an ObjectDataSource control (where we could supply a custom Update method method to handle the updates), or have the user navigate to a new page when performing updates – and display a DetailsView or FormView control that was bound to a Product entity for editing (and not try and do inline editing with the grid).

Summary

You can easily perform common query operations against a LINQ to SQL data model using the built-in declarative filtering support of the LinqDataSource.

To enable more advanced or custom filtering expressions, you can take advantage of the LINQDataSource’s Selecting event.  This will enable you to perform any logic you want to retrieve and filter LINQ to SQL data.  You can call methods to retrieve this data, use LINQ Query Expressions, call a Stored Procedures, or invoke a Custom SQL Expression to-do this.

Hope this helps,

Scott

2 thoughts on “Dùng biểu thức LINQ tùy biến với <asp:LinqDatasource> (LINQ to SQL phần 9)

  1. Cảm ơn anh Nam nhiều nhé. Sự hướng dẫn cặn kẽ của anh về LinQ đã giúp ích cho em rất nhiều.

  2. Em đã copy hết bài của anh, nhưng em chỉ mục đích là học. Cám ơn sự nhiệt tình của anh

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s