views:

27

answers:

1

I have a problem on using LinqToSQL with paging + dynamic sorting. These are my sample code.

Using db As New MyDataContext(connectionString)
      db.Log = new DebuggerWritter
      Dim result = db.User.OrderBy(Function(u) u.UserId)

      result = result.Skip((pageNo - 1) * pageSize).Take(pageSize)      
End Using

This is the SQL script generated by LINQToSQL, which is only retrieve certain row of records.

SELECT [t1].[UserId], [t1].[UserName]
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[UserId]) AS [ROW_NUMBER], [t0].[UserId], [t0].[UserName]
    FROM [dbo].[User] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]

But if i implemented dynamic sort,

Using db As New MyDataContext(connectionString)
      db.Log = new DebuggerWritter
      Dim result = db.User

      For Each s In sortExpressions

          Dim expression As Func(Of User, Object) = Function(u) u.[GetType]().GetProperty(s.propertyName).GetValue(u, Nothing)

          Select Case s.SortOrder
               Case SortExpression.SortDirection.Ascending
                    result = result.OrderBy(expression).AsQueryable
               Case SortExpression.SortDirection.Descending
                    result = result.OrderByDescending(expression).AsQueryable
          End Select
      Next

      result = result.Skip((pageNo - 1) * pageSize).Take(pageSize)  
End Using

This is the SQL script generated this time,

SELECT [t0].[UserId], [t0].[UserName]
FROM [dbo].[User] AS [t0]

How come the paging control script is not generated and the sorting is gone?? Is that the way i implement dynamic sorting is wrong? or LinqToSQL did not support paging + dynamic sorting?? Help!!!

A: 

What you're doing in your code will not work. However, I'm a bit surprised that LINQ to SQL doesn't throw an exception.

The OrderBy method that you call in result.OrderBy(expression), is the Enumerable.OrderBy method and not the Queryable.OrderBy method. You already noticed this, because the object it returns is an IEnumerable(Of User) and not an IQueryable(Of User). For this reason you are converting it to an IQueryable(Of User) by calling AsQueryable(). Calling AsQyeryable however, cannot work. The reason for this is that LINQ to SQL processes expression trees (what an IQueryable basically is). And expression trees can be dynamically combined, as you already do with result = result.Skip. Enumerable however, doesn't work with expression trees, but with delegates, which are compiled method calls. While you can convert such a compiled method call to an IQueryable, LINQ to SQL cannot analyze it, because it still is compiled code (it would take introspection to do this, which only specialized tools as Reflector support). In this case LINQ to SQL probably ignored the complete order by part of your query, because it found a (compiled) method call it couldn't process.

So, you're solution will only work when you do the following:

Dim expression As Expression(Of Func(Of User, [ExpectedKeyHere]) = _
   ...  creation of the expression here ...

Select Case s.SortOrder
    Case SortExpression.SortDirection.Ascending
        result = result.OrderBy(expression)
    Case SortExpression.SortDirection.Descending
        result = result.OrderByDescending(expression)
End Select

There is however -yet- another thing. You seem to loop through a set of sortExpressions. While it is possible to append an already sorted collection, an already sorted collection must be called with result.ThenBy or result.ThenByDescending. Calling it with result.OrderBy will completely resort it; you will loose the initial sorting order.

To make a long story short, perhaps you should try to build some sort of EntitySorter object that allows callers of your method to specify the sort order. Your service method could than look like this (sorry, it's C#):

public static Person[] GetAllPersons(IEntitySorter<Person> sorter)
{
    Condition.Requires(sorter, "sorter").IsNotNull();

    using (var db = ContextFactory.CreateContext())
    {
        IOrderedQueryable<Person> sortedList =
            sorter.Sort(db.Persons);

        return sortedList.ToArray();
    }
}

I've written a blog about exactly this, again, it's C#, but I think it will do exactly what you need.

Good luck.

Steven
Thanks, it's really working for me.
Kevin