Разбиение на страницы .NET MVC Linq с помощью PagedList

В моем приложении MVC я использую LINQ для извлечения данных из БД и PagedList для разбиения на страницы. У меня есть пара вопросов после блока кода, с которыми мне нужна помощь.

Функция извлечения данных из кеша или БД:

public NewsPagedListDTO GetNewsFromCacheOrDB(int pageSize, int? newsID, int? page, string newsTitle, int? categoryID, int? orderByTitle, int? orderByPublisher, int? orderByDate, int? orderByCategory)
            {
            DataCache cache = new DataCache("default");
            object cacheNews = cache.Get("cacheNews");

            List<News> news = new List<News>();


            if (cacheNews == null)
            {
                news = (from n in DB.News
                        select n).ToList();

            //Only cache if no parameters was provided
            if (newsID == null && newsTitle == null && categoryID == null && orderByTitle == null && orderByPublisher == null &&
                orderByDate == null && orderByCategory == null)
                cache.Add("cacheNews", news);
            }
            }
            else
            {
                news = (List<News>)cacheNews;
            }

            if (newsID != null)
                news = news.Where(n => n.NewsID == newsID).ToList();

            if (categoryID != null)
                news = news.Where(n => n.CategoryID == categoryID).ToList();

            if (newsTitle != null)
                news = news.Where(n => n.Title == newsTitle).ToList();

            if (orderByTitle != null)
                if (orderByTitle == 0)
                    news = news.OrderBy(n => n.Title).ToList();
                else
                    news = news.OrderByDescending(n => n.Title).ToList();

            if (orderByPublisher != null)
                if (orderByPublisher == 0)
                    news = news.OrderBy(n => n.PublishedByFullName).ToList();
                else
                    news = news.OrderByDescending(n => n.PublishedByFullName).ToList();

            if (orderByDate != null)
                if (orderByDate == 0)
                    news = news.OrderByDescending(n => n.DatePublished).ToList();
                else
                    news = news.OrderBy(n => n.DatePublished).ToList();

            if (orderByCategory != null)
                if (orderByCategory == 0)
                    news = news.OrderBy(n => n.CategoryToString).ToList();
                else
                    news = news.OrderByDescending(n => n.CategoryToString).ToList();


            List<NewsDTO> newsDTO = new List<NewsDTO>();

            foreach (var item in news)
            {
                NewsDTO newsDTOtemp = new NewsDTO();

                newsDTOtemp.BlobName = item.BlobName;
                newsDTOtemp.DatePublished = item.DatePublished;
                newsDTOtemp.NewsID = item.NewsID;
                newsDTOtemp.PreviewText = item.PreviewText;
                newsDTOtemp.PublishedByEmail = item.PublishedByEmail;
                newsDTOtemp.PublishedByFullName = item.PublishedByFullName;
                newsDTOtemp.PublishedByID = item.PublishedByID;
                newsDTOtemp.Title = item.Title;
                newsDTOtemp.CategoryID = item.Category.CategoryID;
                newsDTOtemp.CategoryToString = item.Category.Name;

                newsDTO.Add(newsDTOtemp);
            }

            //Pagination
            NewsPagedListDTO newsResultDTO = new NewsPagedListDTO();
            newsResultDTO.NewsDTO = (PagedList<NewsDTO>)newsDTO.ToPagedList(page ?? 1, pageSize);

            return newsResultDTO;
        }

Разбивка на страницы, на мой взгляд:

@Html.PagedListPager(Model.NewsPagedListDTO.NewsDTO, page => Url.Action("News", new
   {
       page,
       newsTitle = Request.QueryString["NewsTitle"],
       categoryID = Request.QueryString["categoryID"],
       orderByTitle = Request.QueryString["orderByTitle"],
       orderByPublisher = Request.QueryString["orderByPublisher"],
       orderByDate = Request.QueryString["orderByDate"],
       orderByCategory = Request.QueryString["orderByCategory"]
   }),
    new PagedListRenderOptions()
    {
        Display = PagedListDisplayMode.IfNeeded,
        MaximumPageNumbersToDisplay = 5,
        DisplayEllipsesWhenNotShowingAllPageNumbers = false,
        DisplayLinkToPreviousPage = PagedListDisplayMode.Never,
        DisplayLinkToNextPage = PagedListDisplayMode.Never,
        LinkToFirstPageFormat = String.Format("«"),
        LinkToLastPageFormat = String.Format("»")
    })

Вопросы

  1. Я впервые использую PagedList. Какой смысл иметь обратную передачу для изменения страницы при получении полных результатов? Разве тогда не лучше с нумерацией страниц на стороне клиента? В настоящее время я получаю все сообщения из БД с помощью:

    новости = (из n в DB.News выберите n).ToList();

    И после того, как данные получены, отсортируйте по параметрам.

    Конечно, результат легко кэшировать, но... Я предпочитаю получать данные только для одной страницы.

  2. Как мне получить данные только для текущей страницы с моими необязательными параметрами? Раньше я использовал для этого хранимые процедуры, но не думаю, что это возможно с PagedList.

  3. Как получить более чистый код для необязательных параметров в моем запросе LINQ? Мне не нравятся все эти операторы if..


person Reft    schedule 05.05.2015    source источник


Ответы (1)


Дело в том, что вам нужно Skip элементов, а затем Take(pageSize)

var pagedNews = DB.News.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();

Допустим, у вас есть 5 элементов на странице.

Если вы находитесь на странице 1

(1 - 1) * 5 = 0 поэтому пропустите ноль предметов и возьмите 5

Если вы находитесь на странице 2

(2 - 1) * 5 = 5 поэтому пропустите 5 пунктов и возьмите 5

Ваши параметры Nullable, поэтому вам, возможно, придется поставить условие по умолчанию для ваших параметров, например, если NULL, то PageSize = 5 и PageNumber = 1

int pageSize, int? newsID, int? page

ИЗМЕНИТЬ:

Вместо:

if (cacheNews == null)
{
                news = (from n in DB.News
                        select n).ToList();

...........
}

Использовать это:

// Вы должны будете использовать OrderBy() перед разбиением на страницы:

// Read as Queryable()

var pagedNews = DB.News.AsQueryable();

// Apply OrderBy Logic
pagedNews = pagedNews.OrderBy(); 

//ApplyPagination
pagedNews = pagedNews.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();

ЗАКАЗАТЬ ПО

Вам не нужно передавать столбцы OrderBy как отдельные строки.

Передайте одну строку, например. selectedSortBy из просмотра,

Я создал вспомогательный метод:

using System;
using System.Linq;
using System.Linq.Expressions;

namespace Common.Helpers
{
    public static class PaginationHelper
    {
        public static IQueryable<T> ApplyPagination<T>(IQueryable<T> source, Pagination pagination)
        {
            var sortDirection = pagination.SortDirection == SortDirectionEnum.Ascending ? "OrderBy" : "OrderByDescending";
            var orderBy = pagination.SortBy ?? pagination.DefaultSortBy;

            return source.OrderBy(orderBy, sortDirection).Skip((pagination.PageNumber - 1) * pagination.PageSize).Take(pagination.PageSize);
        }

        public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, string sortDirection, params object[] values)
        {
            var type = typeof(T);
            var property = type.GetProperty(ordering);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var orderByExp = Expression.Lambda(propertyAccess, parameter);
            var resultExp = Expression.Call(typeof(Queryable), sortDirection, new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
            return source.Provider.CreateQuery<T>(resultExp);
        }
    }
}

Модель разбиения на страницы + перечисление:

namespace Common.Helpers
{
    public class Pagination
    {
        public SortDirectionEnum SortDirection { get; set; }
        public string SortBy { get; set; }
        public int TotalRecords { get; set; }
        public int NumberOfPages { get; set; }
        public int PageSize { get; set; }
        public int PageNumber { get; set; }

        public string DefaultSortBy { get; set; }

        public string ReloadUrl { get; set; }
        public string TargetDiv { get; set; }

        public Pagination()
        {

        }

        public Pagination(string reloadUrl, string targetDiv, int totalRecords, int numberOfPages)
        {
            ReloadUrl = reloadUrl;
            TargetDiv = targetDiv;
            PageSize = 10;
            PageNumber = 1;
        }
    }

    public enum SortDirectionEnum
    {
        Ascending = 1,
        Descending = 2
    }
}

Затем вызовите свой запрос следующим образом:

var items = DB.News.AsQueryable();

items = PaginationHelper.ApplyPagination(items, PAGINATION_MODEL);
person Dawood Awan    schedule 05.05.2015
comment
Привет! Разбивка на страницы работает как надо, нулевой номер страницы проверяется с помощью ?? поэтому значение по умолчанию равно 1, так что это не проблема. Мне было интересно, разве я не извлекаю ВСЕ сообщения из БД в моем первом запросе, а ЗАТЕМ сортирую и реализую разбиение на страницы? Это означает, что нумерация страниц бесполезна, если я хочу уменьшить нагрузку на базу данных. - person Reft; 05.05.2015
comment
В этом запросе: Skip((currentPage - 1) * pageSize).Take(pageSize) вы не выбираете все значения в БД. вы выбираете только pageSize - person Dawood Awan; 05.05.2015
comment
@Reft Я включил логику CustomSort - person Dawood Awan; 05.05.2015
comment
Потрясающе спасибо. При переходе на запрашиваемый я столкнулся с некоторыми проблемами с сохранением кеша как запрашиваемого, а мое свойство навигации в новостях не получает никаких данных (null). Если вы знаете что-нибудь об этом, пожалуйста, не стесняйтесь поделиться этим, иначе просто оставьте это :) Но! Последний вопрос, прежде чем я приму ваш ответ. Не могли бы вы пояснить, почему теперь вы используете queryable вместо списка? - person Reft; 05.05.2015
comment
Iqueryable сгенерирует запрос типа SQL, и в конце, когда вы вызовете .tolist, он выполнит сгенерированный queryable - person Dawood Awan; 05.05.2015
comment
Так что нет ничего плохого в вызове .tolist и включении моего свойства навигации в конце? - person Reft; 05.05.2015
comment
Вызов .tolist в конце имеет то преимущество, что не все данные загружаются в память при запуске. Когда вы вызываете .tolist в конце, он попадет в базу данных после выполнения сгенерированного запроса. - person Dawood Awan; 05.05.2015
comment
stackoverflow.com/questions/5416375/ - person Dawood Awan; 05.05.2015
comment
Ваши пользовательские сортировщики выдают мне ошибку: Аргументы типа для метода System.Linq.Queryable.OrderBy‹TSource,TKey›(System.Linq.IQueryable‹TSource›, System.Linq.Expressions.Expression‹System.Func‹TSource,TKey ››)» нельзя вывести из употребления. Попробуйте явно указать аргументы типа. - person Reft; 05.05.2015
comment
Извините, я забыл, что использовал библиотеку dyamic.LINQ в этом проекте. Я опубликовал решение, не использующее эту библиотеку, вы можете отредактировать его, чтобы оно соответствовало вашему решению. - person Dawood Awan; 05.05.2015