0

In my MVC app I'm using LINQ to retrieve data from DB and PagedList for pagination. I have a couple of questions, after the code block, that I would like some help with.

Function where I retrieve data from cache or DB:

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;
        }

Pagination in my view:

@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("»")
    })

Questions

  1. It's the first time I'm using PagedList. What's the point in having postback for changing the page when the full results is retrieved? Isn't it better with client side pagination then? Currently I am retrieving all posts from DB with:

    news = (from n in DB.News select n).ToList();

    And after data is retrieved, sort with parameters..

    Sure the result is easy to cache but.. I rather only get data for just one page.

  2. How would I only get data for the current page with my optional parameters? I have used stored procedures for this before but I don't think it's possible with PagedList.

  3. How can I have cleaner code for optional parameters in my LINQ query? I don't like all those if statements..

Reft
  • 2,333
  • 5
  • 36
  • 64

1 Answers1

1

The thing is you have to Skip items and then Take(pageSize)

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

So let's say you have 5 items / page.

If you are on page 1

(1 - 1) * 5 = 0 so skip zero Items and take 5

If you are on page 2

(2 - 1) * 5 = 5 so skip 5 Items and take 5

Your parameters are Nullable so you might have to put a default condition on your parameters say if NULL then PageSize = 5 and PageNumber = 1

int pageSize, int? newsID, int? page

EDIT:

Instead of:

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

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

Use this:

// You will have to OrderBy() before doing the pagination:

// Read as Queryable()

var pagedNews = DB.News.AsQueryable();

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

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

ORDER BY

You don't need to pass the OrderBy columns as separate strings.

Pass one string e.g. selectedSortBy from View,

I have created a Helper method:

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);
        }
    }
}

Pagination Model + Enum:

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
    }
}

Then call your Query like this:

var items = DB.News.AsQueryable();

items = PaginationHelper.ApplyPagination(items, PAGINATION_MODEL);
Dawood Awan
  • 7,051
  • 10
  • 56
  • 119
  • Hi! The pagination is working as it should, the nullable pageNumber is checked with ?? so the default value is 1 so that is not a problem. What i was wondering is, don't I retrieve _ALL_ posts from db in my first query and THEN sort and implement pagination? Meaning the pagination is useless if i want to relieve stress on the database. – Reft May 05 '15 at 11:09
  • In this query: Skip((currentPage - 1) * pageSize).Take(pageSize) you are not selecting all the values in the DB. you are only selecting pageSize – Dawood Awan May 05 '15 at 11:10
  • @Reft I have included CustomSort Logic – Dawood Awan May 05 '15 at 11:23
  • Awesome thank you. When changing to queryable I ran into some problems with saving the cache as queryable and my navigationproperrty in news not getting any data, (null). If you know anything about this please feel free to share it, else just leave it:) But! One last question before i accept your answer. Could you elaborate why you use queryable now instead of list? – Reft May 05 '15 at 11:32
  • Iqueryable will generate a SQL type query and at the end when you call .tolist it will execute the generated iqueryable – Dawood Awan May 05 '15 at 11:34
  • So it is nothing wrong with calling .tolist and include my navigation property at the end? – Reft May 05 '15 at 11:36
  • Calling .tolist at the end has advantage that not all the data is loaded into memory at start. When you call .tolist at the end it will hit the database once executing generated query – Dawood Awan May 05 '15 at 11:38
  • http://stackoverflow.com/questions/5416375/differences-when-using-ienumerable-and-iqueryable-as-a-type-of-objectset – Dawood Awan May 05 '15 at 11:41
  • Your custom sorters gives me error: The type arguments for method 'System.Linq.Queryable.OrderBy(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly. – Reft May 05 '15 at 16:47
  • So sorry, I forgot I was using dyamic.LINQ library in that project. I have posted a solution not using that library, you can edit it to match with your solution – Dawood Awan May 05 '15 at 18:02