Пейджинг с помощью PagedList, эффективно ли это?

Я уже давно пытаюсь реализовать пейджинг и нашел этот учебник для пейджинга с помощью MVC: Пейджинг ASP.NET MVC выполнен идеально

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

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

Если я понимаю что-то не так, пожалуйста, не стесняйтесь меня резать прямо сейчас, но для меня это решение совсем не идеальное.

Я что-то неправильно понял? Есть ли более эффективное решение или библиотека для разбивки на страницы с помощью MVC?


person Flame_Phoenix    schedule 08.04.2015    source источник
comment
Как вы запрашиваете БД?   -  person JonE    schedule 08.04.2015
comment
Учебное пособие предназначено для демонстрации и выполняет разбиение на страницы со стороны клиента, при котором он получает все данные за один раз, а затем выполняет разбиение на страницы только из этого набора данных. В вашем случае, имея много данных, вы должны использовать разбиение на страницы на стороне сервера.   -  person Ruchir Shah    schedule 08.04.2015
comment
@ Glitch100: я запрашиваю БД точно так же, как в примере: List<Client> allClients = DB.Client.ToList();.   -  person Flame_Phoenix    schedule 08.04.2015
comment
@RuchirShah: Есть ли хорошие библиотеки для использования MVC C#? Вы рекомендуете какой-нибудь?   -  person Flame_Phoenix    schedule 08.04.2015
comment
@Flame_Phoenix: Делать особо нечего, нужно передать некоторые параметры на сервер и запросить БД в соответствии с ними. Пошаговый пример, вы найдете все из здесь   -  person Ruchir Shah    schedule 08.04.2015
comment
У меня есть решение, но оно зависит от того, для чего вы используете это действие? Нужно ли обновлять данные зарегистрированных участников?   -  person JonE    schedule 10.04.2015


Ответы (7)


Естественно, для подкачки потребуется знание общего количества результатов, чтобы логика могла определить, сколько страниц имеется и т. д. Однако вместо того, чтобы сводить все результаты, просто создайте запрос к базе данных, чтобы вернуть количество выгружаемых страниц (например, 30) и как а также подсчет всех результатов.

Например, если вы использовали Entity Framework или LINQ2SQL, вы могли бы сделать что-то вроде этого

IQueryable<Result> allResults = MyRepository.RetrieveAll();

var resultGroup = allResults.OrderByDescending(r => r.DatePosted)
                                               .Skip(60)
                                               .Take(30)
                                               .GroupBy(p => new {Total = allResults.Count()})
                                               .First();

var results = new ResultObject
{
    ResultCount = resultGroup.Key.Total,
    Results = resultGrouping.Select(r => r)
};

Поскольку мы не выполнили .ToList() для нашего набора результатов, пока не завершили то, что нам нужно, мы не занесли результаты в память. Это делается, когда мы вызываем .First() для нашего набора результатов.

Наконец, наш объект, который у нас получился (ResultObject), можно использовать для последующего пейджинга. Поскольку у нас есть подсчет, мы уже знаем, на какой странице мы находимся (3, поскольку мы пропустили 60, по 30 на страницу), и у нас есть результаты для отображения.

Дополнительная литература и информация

Как пролистать результаты запроса

пейджинг на стороне сервера с Entity Frame

person JonE    schedule 08.04.2015

Если вы перейдете на страницу github PagedList аддона, вы увидите, что если у вас есть метод, возвращающий IQueryable<T> тогда магия PagedList может работать над этим, не возвращая каждый элемент из базы данных. Если вы не можете контролировать, что возвращает вам запрос из базы данных, вам придется полагаться на другие методы.

Пример с этой страницы таков

public class ProductController : Controller
{
    public object Index(int? page)
    {
        var products = MyProductDataSource.FindAllProducts(); //returns IQueryable<Product> representing an unknown number of products. a thousand maybe?

        var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
        var onePageOfProducts = products.ToPagedList(pageNumber, 25); // will only contain 25 products max because of the pageSize

        ViewBag.OnePageOfProducts = onePageOfProducts;
        return View();
    }
}
person Loofer    schedule 08.04.2015

Пример на github показывает, что он использует IQueryable, который затем используется ToPagedList(), что подразумевает, что код довольно оптимизирован и сам по себе не возвращает все записи...

Глядя на код класса PagedList

// superset is the IQueryable.
TotalItemCount = superset == null ? 0 : superset.Count();

// add items to internal list
if (superset != null && TotalItemCount > 0)
    Subset.AddRange(pageNumber == 1
    ? superset.Skip(0).Take(pageSize).ToList()
    : superset.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList()

Итак, как вы можете видеть, он уже использует рекомендуемые методы пейджинга на стороне сервера skip и take, а затем предварительно формирует ToList().

Однако, если вы не работали с IQueryable, то есть с IEnumerable, тогда используется следующий код:

/// <summary>
/// Initializes a new instance of the <see cref="PagedList{T}"/> class that divides the supplied superset into subsets the size of the supplied pageSize. The instance then only containes the objects contained in the subset specified by index.
/// </summary>
/// <param name="superset">The collection of objects to be divided into subsets. If the collection implements <see cref="IQueryable{T}"/>, it will be treated as such.</param>
/// <param name="pageNumber">The one-based index of the subset of objects to be contained by this instance.</param>
/// <param name="pageSize">The maximum size of any individual subset.</param>
/// <exception cref="ArgumentOutOfRangeException">The specified index cannot be less than zero.</exception>
/// <exception cref="ArgumentOutOfRangeException">The specified page size cannot be less than one.</exception>
public PagedList(IEnumerable<T> superset, int pageNumber, int pageSize)
        : this(superset.AsQueryable<T>(), pageNumber, pageSize)
    {
    }

Проблема в том, что в зависимости от фильтрации, используемой для получения IEnumerable, в первую очередь могут содержаться все записи, поэтому по возможности используйте IQueryable для оптимальной производительности PagedList.

person Paul Zahra    schedule 08.04.2015

Связанный учебник выглядит странно, потому что он использует List<Client>. Это действительно приведет к тому, что все клиенты будут сохранены в памяти, а затем просмотрены. Вместо этого вам следует искать методы, использующие IQueryable<T>, в частности Skip и Take, поэтому разбиение по страницам должно выглядеть так:

IQueryable<Client> clients = repo.GetClients();          // lazy load - does nothing
List<Client> paged = clients.Skip(20).Take(10).ToList(); // execute final SQL

В зависимости от того, какие преобразователи вы используете, вы найдете аналогичные методы в EF, NHibernate, Linq-to-SQL и т. д.

person oleksii    schedule 08.04.2015

У вас есть три способа реализовать пагинацию в вашем приложении:

  • Заставьте ваш Repository возвращать DTO с минимальным объемом данных обратно клиенту и используйте некоторые из jquery плагинов, обеспечивающих разбивку на страницы сами по себе. Это простой способ, но иногда (как в вашем случае) это не вариант. Поэтому вам нужно реализовать разбиение на страницы на стороне сервера.
  • Кэшировать всю коллекцию и возвращать нужную страницу с расширением LINQ. Я думаю, что многие репозитории и ORM делают это внутри (не работал с Entity Framework, не могу сказать точно). Проблема с этим решением заключается в том, что вам нужно синхронизировать кеш и базу данных, и вам нужно получить сервер с достаточным объемом памяти для хранения всех ваших данных (или перейти в облако или что-то в этом роде). Как и в других ответах, вы можете пропустить ненужные данные с помощью ленивой работы IEnumerable, поэтому вам не нужно кэшировать коллекцию.
  • Реализовать пагинацию на стороне БД. Если вы используете SQL, вы можете использовать конструкцию ROW_NUMBER, она работает либо в MS SQL или в Oracle или в MySQL (на самом деле не ROW_NUMBER, а только аналог). Если у вас есть решение NoSQL, вам необходимо проверить его документацию.
person VMAtm    schedule 08.04.2015

Этот компонент (PagedList) отлично работает для большого количества записей, в первый раз и каждый раз при выборе страницы он будет делать 2 обращения к базе данных. Один возвращает количество записей, а другой возвращает только записи выбранной страницы. Просто убедитесь, что вы не вызываете метод ToList()

person foluis    schedule 05.08.2016

  1. Код с объектом Webgrid в .cshtml, и все будет в порядке.
  2. Сложность пейджинга будет довольно низкой.
  3. чистый код.
  4. Класс Micro Soft BCL. меньше багов.
person ManjunathMayurRK    schedule 05.08.2016