вторник, 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)
Пример использования выше. Получилось гламурно :). Если будут вопросы пишите.

ReportViewer не работает под IIS 7

Недавно пришлось использовать компонент ReportViewer в ASP.NET.
У меня на ноутбуке стоит Vista Business и IIS7.
При открытии страницы в IE, FireFox - получаю кучу JS ошибок. Проблема кроется в новой структуре Web.config файла для IIS 7.
Когда вы добавляете на страницу компонент ReportViewer, студия добавляет ссылку на http handler для этого компонента. Это нужно для того, чтоб ASP.NET использовало сборку Microsoft.Reporting.WebForms.HttpHandler при обращении к ReportViewerWebControl.axd:

<system.web>
<httpHandlers>
<add path="Reserved.ReportViewerWebControl.axd"
verb="*"
type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
validate="false"/>
<httpHandlers>
<system.web>



В новой структуре секция system.web\httpHandlers теперь перенесена в system.webServer\handlers, но студия редактирует конфиг как для IIS 6.

Для решения проблемы надо создать соответствующую секцию:

<system.webServer>
<handlers>
   <add name="ReportViewerWebControl"
           path="Reserved.ReportViewerWebControl.axd" 
verb="*"
           type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
   <handlers>
<system.webServer>


сross post: http://blogs.msdn.com/vijaysk/archive/2009/01/14/report-viewer-toolbar-does-not-render-properly-on-iis-7-0.aspx