вторник, 28 апреля 2009 г.

LINQ dynamic expressions. OrderBy

В одном из проектов я писал клиентский компонент который отображал табличные данные (грид). Эти данные приходили из веб сервиса и передавались в грид.

Веб сервис выглядел так:

public List<Student> GetStudents()
{
     var query = (from student in db.Students
                  select new
                  {
                       student.StudentID,
                       student.LastName,
                       student.FirstName,
                       ...
                  }
     ...
}

Потом добавилось требования сортировать грид по нажатию на шапку колонки. Данные о сортируемой колонке должны были передаваться в сервис:
public List<Student> GetStudents(string column, bool asc)

и тут стал вопрос "а как ?", если у меня анонимный тип. Это потом я уже собираю в Student:
Вариант в лоб:

if (!asc)
{
     switch(column)
     {
          case "FirstName": 
         query = query.OrderByDescending(p => p.FirstName);
          break;
    
          case "LastName": 
         query = query.OrderByDescending(p => p.LastName);
          break;
          ...
          ... 
    // All columns
     }
}
else
{
     switch(column)
     {
          case "FirstName": 
         query = query.OrderBy(p => p.FirstName);
          break;

          case "LastName": 
         query = query.OrderBy(p => p.LastName);
          break;
          ...
          ... 
    // All columns
     }
}

Что, не? Та да... :)

Второй вариант и наверно единственный (если вы не мазохист) - это использовать LINQ Expressions. Что оно такое и как работает читайте в msdn или на блогах. Я напишу свое решение относительно моих требований и условий. В результате получится:

query = query.OrderBy(column, asc);

Для того чтоб все казалось как "так и надо" я использовал Extension Methods, которые появились в .NET 3.0.

public static class OrderByExtension
{
     /// <summary>Orders the sequence by specific column and direction.</summary>
     /// <param name="query">The query.</param>
     /// <param name="sortColumn">The sort column.</param>
     /// <param name="ascending">if set to true [ascending].</param>
     public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string sortColumn, string direction)
     {
          string methodName = string.Format("OrderBy{0}", direction.ToLower() == "ascending" ? "" : direction);

          // .OrderBy{Direction}(p => p.{ColumnName})
          PropertyInfo property = query.ElementType.GetProperty(sortColumn);
          ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
          MemberExpression paramAccess = Expression.MakeMemberAccess(parameter, property);
          LambdaExpression orderByLambda = Expression.Lambda(paramAccess, parameter);

          MethodCallExpression result = Expression.Call(
                    typeof(Queryable),
                    methodName,
                    new[] { query.ElementType, property.PropertyType },
                    query.Expression,
                    Expression.Quote(orderByLambda));

          return query.Provider.CreateQuery<T>(result);
     }
}

В общем виде формирование запроса разбивается на следующие этапы:
  1. Определяем метод сортировки OrderBy или OrderByDescending.
  2. Получить ссылку на поле, которое участвует в результирующей выборке.
  3. Далее формируем lambda выражение: p => p.[ColumnName], где в [ColumnName] будет подставлено поле, которое мы получили в пункте 2.
  4. Создаем вызов метода OrderBy например, который использует labda из пункта 3.: OrderBy(p => p.[ColumnName])
  5. Внедряем наш метод в цепочку вызовов: return query.Provider.CreateQuery(result)
Пример использования выше. Получилось гламурно :). Если будут вопросы пишите.

Комментариев нет:

Отправить комментарий