Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Разбиение на страницы на серверной стороне с применением Entity Framework и ASP.NET MVC 3 RSS

Разбиение на страницы на серверной стороне с применением Entity Framework и ASP.NET MVC 3

Текущий рейтинг: 2.5 (проголосовало 2)
 Посетителей: 1658 | Просмотров: 3848 (сегодня 0)  Шрифт: - +
В прошлой рубрике я продемонстрировала плагин jQuery DataTables и его способность обрабатывать огромные объемы данных на клиентской стороне. Он отлично работает с веб-приложениями, где нужно создавать продольные и поперечные срезы в больших массивах данных. На этот раз я сосредоточусь на использовании запросов, которые возвращают меньшие порции данных для поддержки различных типов интерактивного взаимодействия с данными. Это особенно важно в случае мобильных приложений.

Я задействую преимущества средств, введенных в ASP.NET MVC 3, и покажу, как использовать их совместно с эффективной поддержкой разбиения на страницы на серверной стороне с применением Entity Framework. Эта задача требует решения двух проблем. Первая — сформировать запрос Entity Framework с корректными параметрами разбиения на страницы. Вторая — имитировать поддержку разбиения на страницы на клиентской стороне, предоставив визуальные подсказки о возможности извлечения следующей порции данных и ссылки для инициации их извлечения.

В ASP.NET MVC 3 довольно много новых средств, например новый механизм представлений Razor, усовершенствования проверки и тонна дополнительных средств на основе JavaScript. Главная страница MVC находится по адресу asp.net/mvc, где вы можете скачать ASP.NET MVC 3 и найти ссылки на соответствующие публикации в блогах, а также на обучающие видеоролики, которые позволят вам быстрее приступить к работе. Одно из новых средств, которым я воспользуюсь, — ViewBag. Если вы уже работали с ASP.NET MVC, то имейте в виду, что ViewBag — это расширение класса ViewData и что оно позволяет использовать динамически создаваемые свойства.

Другой новый элемент в ASP.NET MVC 3 — специализированный System.Web.Helpers.WebGrid. Хотя одной из функций сетки является разбиение на страницы, я применю в своем примере лишь саму сетку, но не ее функцию разбиения на страницы, так как эта функция работает исключительно на клиентской стороне; иначе говоря, он позволяет «пролистывать» только предоставленный набор данных по аналогии с плагином DataTables.Я же хочу задействовать разбиение на страницы на серверной стороне.

Для этого небольшого приложения понадобится Entity Data Model. Я использую ту модель, которая создается по образцу базы данных Microsoft AdventureWorksLT, но мне потребуются в модели лишь Customer и SalesOrderHeaders. Я переместила Customer-свойства rowguid, PasswordHash и PasswordSalt в отдельную сущность, чтобы не беспокоиться о них при редактировании. Помимо этого, я больше ничего не модифицировала в модели по умолчанию.

Я создала проект, используя шаблон проектов ASP.NET MVC 3 по умолчанию. Это обеспечивает предварительное заполнение ряда контроллеров и представлений, поэтому стандартный HomeController представляет Customers.

Я буду использовать простой класс DataAccess для взаимодействия с моделью, контекстом и соответственно с базой данных. В этом классе мой метод GetPagedCustomers обеспечивает разбиение на страницы на серверной стороне. Если бы целью приложения ASP.NET MVC было разрешить пользователю взаимодействовать со всеми клиентами, тогда пришлось бы иметь дело с очень большим количеством объектов-клиентов (customers), возвращаемых одним запросом, и управлять ими всеми в браузере. Вместо этого мы отображаем в приложении 10 строк единовременно, а необходимый фильтр предоставляет GetPagedCustomers. Запрос, который в конечном счете мне понадобится выполнить, выглядит так:

context.Customers.Where(c =>
c.SalesOrderHeaders.Any()).Skip(skip).Take(take).ToList()

Представление будет знать, какую страницу нужно запрашивать, и сообщит эту информацию контроллеру. Тот в свою очередь будет знать, сколько строк должно быть на одной странице. Контроллер рассчитает значение пропуска (skip), используя номер страницы и количество строк на страницу. Вызывая метод GetPagedCustomers, контроллер передаст значение skip, а также количество строк на страницу (значение take). Поэтому, если мы находимся на странице 4, а на каждой странице 10 строк, значение skip будет равно 40, а значение take — 10.

Запрос разбиения на страницы сначала создает фильтр, который запрашивает только те объекты клиентов, в которых есть хотя бы один SalesOrder. Затем, используя LINQ-методы Skip и Take, он вернет данные, которые будут представлять собой подмножество этих объектов клиентов. Полный запрос, включая разбиение на страницы, выполняется в базе данных. Она возвращает лишь то количество строк, которое было указано методом Take.

Запрос состоит из нескольких частей для поддержки кое-каких трюков, которые я буду использовать впоследствии. Вот первый вариант метода GetPagedCustomers, который будет вызываться из HomeController:

public static List<Customer> GetPagedCustomers(int skip, int take)
    {
      using (var context = new AdventureWorksLTEntities())
      {
        var query = context.Customers.Include("SalesOrderHeaders")
          .Where(c => c.SalesOrderHeaders.Any())
          .OrderBy(c => c.CompanyName + c.LastName + c.FirstName);

        return query.Skip(skip).Take(take).ToList();
      }
    }

Метод Index контроллера, вызывающий этот метод, определит количество возвращаемых строк, используя переменную pageSize, и это значение станет аргументом для Take. Метод Index также укажет, откуда следует начинать, исходя из номера страницы, передаваемой в качестве параметра:

public ActionResult Index(int? page)
    {
      const int pageSize = 10;
      var customers=DataAccess.GetPagedCustomers((page ?? 0)*pageSize, pageSize);
      return View(customers);
    }

Это существенно продвигает нас вперед. Разбиение на страницы на серверной стороне полностью готово. С помощью WebGrid в разметке представления Index мы можем отображать клиентов, возвращенных методом GetPagedCustomers. В разметке нужно объявить и создать экземпляр сетки, передав Model, который представляет List<Customer>, переданный при создании представления контроллером. Затем, используя WebGrid-метод GetHtml, можно отформатировать сетку, задав отображаемые столбцы. Я показываю только три свойства Customer: CompanyName, FirstName и LastName. Вас порадует, что при наборе содержимого этой разметки обеспечивается полная поддержка IntelliSense, какой бы синтаксис вы ни использовали — сопоставленный с ASPX-представлениями или новый, поддерживаемый механизмом представлений Razor в MVC 3 (как в следующем примере). В первом столбце содержится Edit ActionLink, чтобы пользователь мог редактировать любой из отображаемых объектов клиентов:

@{
  var grid = new WebGrid(Model);
}
<div id="customergrid">
  @grid.GetHtml(columns: grid.Columns(
    grid.Column(format: (item) => Html.ActionLink
      ("Edit", "Edit", new { customerId = item.CustomerID })),
  grid.Column("CompanyName", "Company"),
  grid.Column("FirstName", "First Name"),
  grid.Column("LastName", "Last Name")
   ))
</div>

Результат показан на рис. 1.

*

Рис. 1. Включение Edit ActionLink в WebGrid

Пока все неплохо. Но это не дает возможности пользователю переходить на другую страницу данных. Добиться этой цели можно разными способами. Один из них — указывать номер страницы в URI (например, http://adventureworksmvc.com/Page/3).Очевидно, вы не захотите просить своих пользователей делать такое. Более понятный механизм — размещение элементов управления разбиением на страницы, скажем, ссылок на номера страниц «1 2 3 4 5…» или ссылок, указывающих смещение вперед и назад (вроде «<<   >>»).

На данный момент поддержке ссылок на страницы препятствует то, что странице представления Index не известно, что должно быть запрошено больше объектов клиентов. Все, что она знает, — десять клиентов, отображаемых в текущий момент. Добавив дополнительную логику в уровень доступа к данным и передавая его представлению с помощью контроллера, вы можете решить эту проблему. Начнем с логики доступа к данным.

Чтобы узнавать, имеются ли еще записи помимо текущего набора клиентов, вам потребуется счетчик, указывающий общее количество всех клиентов, который запрос мог бы вернуть без разбиения на группы по 10 клиентов.Вот здесь и придется кстати формирование запроса в методе GetPagedCustomers. Заметьте, что первый запрос возвращается в _customerQuery — переменной, объявленной на уровне класса:

_customerQuery = context.Customers.Where(c => c.SalesOrderHeaders.Any());

Вы можете добавить метод Count в конец этого запроса, чтобы получить количество всех объектов Customer, удовлетворяющих критериям запроса до разбиения на страницы. Метод Count приведет к немедленному выполнению относительно простого запроса. Ниже показан запрос, выполняемый в SQL Server и возвращающий единственное значение:

SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
       COUNT(1) AS [A1]
       FROM [SalesLT].[Customer] AS [Extent1]
       WHERE  EXISTS (SELECT
              1 AS [C1]
              FROM [SalesLT].[SalesOrderHeader] AS [Extent2]
              WHERE [Extent1].[CustomerID] = [Extent2].[CustomerID]
       )
)  AS [GroupBy1]

Получив общее количество подходящих записей, вы можете определить, является текущая страница первой, последней или какой-то промежуточной. Затем вы используете эту логику, чтобы решить, какие ссылки следует отображать. Например, если страница не первая, тогда логично вывести ссылку для доступа к предыдущим страницам («<<»).

Значения, отражающие эту логику, можно вычислить в классе доступа к данным, а потом предоставить их в классе-оболочке наряду с объектами клиентов. Вот новый класс, который я буду использовать:

public class PagedList<T>
  {
    public bool HasNext { get; set; }
    public bool HasPrevious { get; set; }
    public List<T> Entities { get; set; }
  }

Метод GetPagedCustomers теперь будет возвращать класс PagedList, а не List. На рис. 2 показана новая версия GetPagedCustomers.

Рис. 2. Новая версия GetPagedCustomers

public static PagedList<Customer> GetPagedCustomers(int skip, int take)
    {
      using (var context = new AdventureWorksLTEntities())
      {
        var query = context.Customers.Include("SalesOrderHeaders")
          .Where(c => c.SalesOrderHeaders.Any())
          .OrderBy(c => c.CompanyName + c.LastName + c.FirstName);

        var customerCount = query.Count();

        var customers = query.Skip(skip).Take(take).ToList();

        return new PagedList<Customer>
        {
          Entities = customers,
          HasNext = (skip + 10 < customerCount),
          HasPrevious = (skip > 0)
        };
      }
    }

Заполнив новые переменные, посмотрим, как метод Index в HomeController может передать их обратно в View. Здесь можно задействовать новый ViewBag. Мы по-прежнему будем возвращать результаты запроса клиентов в View, но сможем заполнять дополнительные значения, которые помогут определять, как должна выглядеть разметка для следующей и предыдущей ссылок в ViewBag. Затем они станут доступны в View в период выполнения:

public ActionResult Index(int? page)
    {
      const int pageSize = 10;
      var customers=DataAccess.GetPagedCustomers((page ?? 0)*pageSize, pageSize);
      ViewBag.HasPrevious = DataAccess.HasPreviousCustomers;
      ViewBag.HasMore = DataAccess.HasMoreCustomers;
      ViewBag.CurrentPage = (page ?? 0);
      return View(customers);
    }

Важно понимать, что ViewBag является динамическим, а не строго типизированным. Изначально в ViewBag нет HasPrevious и HasMore. Я только что создала их, набирая код. Поэтому не беспокойтесь, что IntelliSense ничего не предлагает насчет них. Вы можете создавать любые нужные вам динамические свойства.

Если вы пользовались словарем ViewPage.ViewData и вам интересно, чем он отличается, то ViewBag делает ту же работу, но, помимо придания вашему коду большей элегантности, обеспечивает типизацию свойств. Например, HasNext является dynamic{bool}, а CurrentPage — dynamic{int}. Вам не придется приводить типы значений после того, как вы их получите.

В разметке у меня по-прежнему есть список клиентов в переменной Model, но имеется и переменная ViewBag. Вводя динамические свойства в разметку, вы остаетесь без поддержки IntelliSense. Всплывающая подсказка напоминает, что свойства динамические, как показано на рис. 3.

*

Рис. 3. Свойства ViewBag недоступны через IntelliSense, так как они динамические

Ниже показана разметка, в которой переменные ViewBag используются для определения того, нужно ли выводить навигационные ссылки:

@{ if (ViewBag.HasPrevious)
  {
    @Html.ActionLink("<<", "Index", new { page = (ViewBag.CurrentPage - 1) })
  }
}

@{ if (ViewBag.HasMore)
   { @Html.ActionLink(">>", "Index", new { page = (ViewBag.CurrentPage + 1) })
  }
}

Эта логика заимствована с некоторыми изменениями из разметки в NerdDinner Application Tutorial, которое можно найти по ссылке nerddinnerbook.s3.amazonaws.com/Intro.htm.

Теперь, запуская приложение, я получаю возможность переходить с одной страницы клиентов на другую.

Если это первая страница, я вывожу ссылку для перехода на следующую страницу, но не на предыдущую, потому что таковой страницы нет (рис. 4).

*

Рис. 4. На первой странице только одна ссылка для перехода на следующую страницу

Щелкнув ссылку и перейдя на следующую страницу, вы увидите ссылки и на предыдущую, и на следующую страницы (рис. 5).

*

Рис. 5. На любой промежуточной странице есть ссылки для перехода на предыдущую и следующую страницы

Следующий этап — это, конечно, работа в дизайнере для того, чтобы сделать «листание» страниц более приятным глазу.

Важная часть вашего инструментария

Подведем итог.Хотя есть ряд инструментов, упрощающих разбиение на страницы на клиентской стороне, например jQuery-расширение DataTables и новая сетка WebGrid в ASP.NET MVC 3, требования вашего приложения могут быть таковы, что вы не получите преимуществ от получения на клиентской стороне больших объемов данных. Поддержка эффективного разбиения на страницы на серверной стороне — важная часть вашего инструментария. В решении этой задачи вам поможет использование Entity Framework совместно с ASP.NET MVC.

Автор: Джули Лерман  •  Иcточник: MSDN Magazine  •  Опубликована: 22.08.2011
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Entity Framework, ASP.NET, MVC.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.