LINQ Cookbook, Recipe 9: Dynamic Sort Order (Doug Rothaus)

LINQ Cookbook, Recipe 9: Dynamic Sort Order (Doug Rothaus)

  • Comments 6

Ingredients:

·         Visual Studio 2008 (Beta2 or Higher)

 

Categories: LINQ-To-XML

 

Introduction:

You can use the Order By clause to easily sort the results of a LINQ query in Visual Basic. A common requirement for sorted data, however, is that the user be able to choose the fields that the results are sorted by. The Order By clause requires that you specify a static list of sort fields and sort directions, so how do you specify a sort order when you don’t know which fields the user has chosen until run time? The solution is to use the OrderBy or OrderByDescending query methods of the Queryable or Enumerable class.

Here’s how it works: The OrderBy or OrderByDescending methods can be applied to any LINQ query. The input to the OrderBy or OrderByDescending methods is either a Lambda expression, or the address of a custom function. The Lambda expression or custom function evaluate an instance of the range variable for the current query scope and return a value to be used when sorting. For example, if the range variable is a Customer object that has FirstName and LastName properties, you can create a function called GetSortValue that takes, as input, a Customer object and returns the value for either the FirstName or LastName property value, based on which field the user selected to sort by.

This recipe shows how to create a custom function to provide values to be used when sorting a LINQ query.

Instructions:

·         Create a new Windows Forms Application in Visual Basic. Set the Size of the default Form, Form1, to 800, 600.

·         From the Toolbox, drag a WebBrowser control onto the form. The name of the WebBrowser control will default to WebBrowser1. In the WebBrowser Tasks panel, select Undock in parent container and resize the WebBrowser control to make room for two Label controls on the form.

·         From the Toolbox, drag a Label control onto the form. Set the Text property of the Label control to Date. The name of the Label control will default to Label1.

·         From the Toolbox, drag another Label control onto the form. Set the Text property of the Label control to Title. The name of the Label control will default to Label2.

·         Right-click the Form and select View Code.

At this point, you will add a method that your application will use to retrieve an RSS feed to use as data to be sorted, and another method that will format the query results as an HTML document to be displayed using the WebBrowser control.

·         In the Form1 class, add the following code.

  Private rss As XElement

 

 

  ' GetRssDocument: Retrieves an RSS XML document from a supplied URL.

 

  Private Function GetRssDocument(ByVal url As String) As XElement

    Dim request = Net.WebRequest.Create(url)

    Dim response = request.GetResponse()

    Dim reader = Xml.XmlReader.Create(response.GetResponseStream())

    reader.MoveToContent()

    Dim rssDoc = XDocument.ReadFrom(reader)

    reader.Close()

    response.Close()

    Return rssDoc

  End Function

 

 

' GetHtmlDocument: Returns HTML transformed from a list of XML elements from

  '                  an RSS feed.

 

  Private Function GetHtmlDocument(ByVal items As IEnumerable(Of XElement))

    Dim htmlDoc = <html>

                    <head>

                      <style>

                        td {font-family:verdana;font-size:12px}

                      </style>

                    </head>

                    <body>

                      <table border="0" cellspacing="2">

                        <%= _

                          From item In items _

                          Select <tr>

                                   <td style="width:480"><b><%= item.<title>.Value %></b></td>

                                   <td><%= item.<pubDate>.Value %></td>

                                 </tr> _

                        %>

                      </table>

                    </body>

 

                  </html>

 

    Return htmlDoc.ToString()

  End Function

Next, you will add a method called GetList that will create the LINQ query. The GetList method takes, as input, an RSS document (typed as XElement), a field name to sort by, and a sort order (either “ASC” or “DESC”). The GetList method starts by creating a query that returns all elements, and then modifies that query using either the OrderBy or OrderByDescending methods, based on the sort direction specified.

·         In the Form1 class, add the following code.

  ' GetList: Returns a list of XML elements from the RSS feed sorted based

  '          on user input.

 

  Private Function GetList(ByVal rss As XElement, _

                           ByVal orderBy As String, _

                           ByVal direction As String) As IEnumerable(Of XElement)

 

    Dim items = From item In rss.<channel>.<item>

 

    If Not String.IsNullOrEmpty(orderBy) Then

      sortField = orderBy

      If Not String.IsNullOrEmpty(direction) AndAlso direction = "DESC" Then

        ' Store the sort order for Label click events

        sortDirection = "DESC"

        ' Sort the query

        items = items.OrderByDescending(AddressOf GetSortValue)

      Else

        ' Store the sort order for Label click events

        sortDirection = "ASC"

        ' Sort the query

        items = items.OrderBy(AddressOf GetSortValue)

      End If

    End If

 

    Return items

  End Function

 

Notice that the GetList method sets two variables that haven’t been declared yet: sortField and sortDirection. These are class-level variables that are used for different reasons. The sortDirection variable is used to toggle the sort order when a user clicks on either the Date label, or the Title label. In the click events for those labels, the code checks to see what the current sort order is, and passes the opposite sort order to the requested query. The sortField variable stores the name of the requested sort field. This is because the OrderBy and OrderByDescending methods only pass the current range variable to the method that returns the sort value. The name of the sort field is not included. As a result, the code stores the requested field in the sortField class-level variable before updating the query with either the OrderBy or OrderByDescending method. When the method that returns the sort value runs, it retrieves the sort field name from the sortField variable.

·         In the Form1 class, add the following declarations after declaring the rss variable.

  Private sortField As String

  Private sortDirection As String

 

The GetList method also passes the address of the GetSortValue method to the OrderBy and OrderByDescending methods, which will be added next. The GetSortValue method returns a value from the current range variable to use when sorting, based on the specified sort field.

·         In the Form1 class, add the following method.

  ' GetSortValue: Based on the current sort field, returns the value for

  '               that field from the supplied XElement. This function is

  '               called by a LINQ query to order a result based on

  '               a user-selected sort field.

 

  Private Function GetSortValue(ByVal item As XElement) As String

    Select Case sortField

      Case "Title"

        Return item.<title>.Value

      Case "Date"

        ' Return a date value as a sortable string

        Dim pubDate = DateTime.Parse(item.<pubDate>.Value)

        Return pubDate.ToString("yyyy/mm/dd hh:mm")

    End Select

 

    Return String.Empty

  End Function

 

Now that you have the supporting variables and methods, you can add the logic to the Form Load event and Label Click events to display and sort the values from the RSS feed.

·         In the Form1 class, add the following code.

  Private Sub Form1_Load() Handles MyBase.Load

    rss = GetRssDocument("http://msdn.microsoft.com/vbasic/rss.xml")

 

    WebBrowser1.DocumentText = GetHtmlDocument(GetList(rss, "Date", "DESC"))

  End Sub

 

 

  Private Sub Label1_Click() Handles Label1.Click

    ' Sort by publish date

 

    ' Toggle the sort direction, default to ascending

    If sortDirection = "DESC" Or sortField = "Title" Then

      WebBrowser1.DocumentText = GetHtmlDocument(GetList(rss, "Date", "ASC"))

    Else

      WebBrowser1.DocumentText = GetHtmlDocument(GetList(rss, "Date", "DESC"))

    End If

  End Sub

 

 

  Private Sub Label2_Click() Handles Label2.Click

    ' Sort by title

 

    ' Toggle the sort direction, default to ascending

    If sortDirection = "DESC" Or sortField = "Date" Then

      WebBrowser1.DocumentText = GetHtmlDocument(GetList(rss, "Title", "ASC"))

    Else

      WebBrowser1.DocumentText = GetHtmlDocument(GetList(rss, "Title", "DESC"))

    End If

  End Sub

Press F5 to see the code run. Click on the Date and Title labels to toggle the sort order of the results.

Leave a Comment
  • Please add 5 and 3 and type the answer here:
  • Post
  • PingBack from http://msdnrss.thecoderblogs.com/2007/10/17/linq-cookbook-recipe-9-dynamic-sort-order/

  • Doug,

    Nice recipe! Isn't is also true you could supply a lambda expression when ordering the XElements, thus eliminating the GetSortValue function?

    Instead of:

    items = items.OrderBy(AddressOf GetSortValue)

    would be:

    items = items.OrderBy(Function(item As XElement) item.Descendants(sortField).Value)

    Or would that not work because of the date parsing you need to do?

    Thanks!

    -B

  • Yup. You can use a Lambda expression as well (as mentioned in the introduction). It's really a choice between the right tool for the right job. As business logic gets more complex, a Lambda expression may get a bit "unwieldy" so encapsulating the logic in a method might be preferable. For simple operations, a Lambda expression would be quite cool. The API documentation for OrderBy and OrderByDescending shows an example using a Lambda, so I decided to show another option.

  • Okay, the whole htmlDoc = <html>….. fragment is pretty interesting. No line continuations in the HTML, embedded script. Very non VB’ish. But I like it. Although completely different, that was the cleanest formatting of an XML document into a table that I have seen.

  • We just released a new set of How-Do-I videos in our LINQ series on LINQ to XML in Visual Basic. These

  • We just released a new set of How-Do-I videos in our LINQ series on LINQ to XML in Visual Basic. These

Page 1 of 1 (6 items)