ASP.NET MVC2 LINQ - Repository pattern, where should the pagination code go?
I'm working on adding an HtmlHelper for pagination, but I am unsure where the proper and/or most beneficial开发者_高级运维 place to put certain parts of the pagination code from a performance and maintainability standpoint.
I am unsure if the Skip(), Take() and Count() portions of Linq to SQL data manipulation should live within the repository or the controller.
I am also unsure if their order and where they are used affects performance in any way.
If they live within the repository from my understanding this is how it would work:
1. I would pass the pageIndex and pageSize as arguments to the repository's method that grabs the data from the database. 2. Then grab the full data set from the database. 3. Then store the count of TotalItems of that full data set in a variable. 4. Then apply the Skip() and Take() so the data set retains only the page I need. 5. Display the partial data set as a single page in the view.If they live in the controller from my understanding this is how it would work: 1. I would grab the full data set from the repository and store it into a variable inside of the controller. 2. Then get the count of TotalItems for the full data set. 3. Then apply the Skip() and Take() so the data set retains only the page I need. 4. Display the partial data set as a single page in the view.
Inside the controller (I realize I will incorrectly get the page count here and not TotalItems):
Character[] charactersToShow = charactersRepository.GetCharactersByRank(this.PageIndex, this.PageSize);
RankViewModel viewModel = new RankViewModel
{
Characters = charactersToShow,
PaginationInfo = new PaginationInfo
{
CurrentPage = this.PageIndex,
ItemsPerPage = this.PageSize,
TotalItems = charactersToShow.Count()
}
};
Inside the repository:
public Character[] GetCharactersByRank(int PageIndex, int PageSize)
{
IQueryable characters = (from c in db.Characters
orderby c.Kill descending
select new Character {
CharID = c.CharID,
CharName = c.CharName,
Level = c.Level
});
characters = PageIndex > 1 ? characters.Skip((PageIndex - 1) * PageSize).Take(PageSize) : characters.Take(PageSize);
return characters.ToArray();
}
This code is a partial example of how I was implementing the Skip(), Take() and Count() code living in the repository. I didn't actually implement getting and returning the TotalItems because that was when I realized I didn't know the proper place to put this.
Part of the reason I am unsure where to put these is that I don't know how Linq to SQL works underneath the hood, and thus I don't know how to optimize for performance. Nor do I know if this is even an issue in this case.
Does it have to grab ALL the records from the database when you do a .Count() on the Linq to SQL? Does it have to make separate queries if I do a .Count(), then later do a .Skip() and .Take()? Is there any possible performance problems with using .Count() prior to a .Skip() and .Take()?
This is my first time using an ORM so I'm not sure what to expect. I know I can view the queries Linq to SQL is running, however I feel that listening to someone with experience in this case would be better use of my time.
I would like to understand this more in depth, any insight would be appreciated.
I keep a generic PaginatedList
class inside my Helpers
folder where I also put other Helper classes.
The PaginatedList is straight out of NerdDinner, and it looks like this.
public class PaginatedList<T>: List<T>
{
public int PageIndex { get; private set; }
public int PageSize { get; private set; }
public int TotalCount { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
PageSize = pageSize;
TotalCount = source.Count();
TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize);
this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
}
public bool HasPreviousPage
{
get
{
return (PageIndex > 0);
}
}
public bool HasNextPage
{
get
{
return (PageIndex + 1 < TotalPages);
}
}
}
I found this on the NerdDinner site that Marko mentioned above and it answered a lot of my questions.
From NerdDinner on the bottom of page 8:
IQueryable is a very powerful feature that enables a variety of interesting deferred execution scenarios (like paging and composition based queries). As with all powerful features, you want to be careful with how you use it and make sure it is not abused.
It is important to recognize that returning an IQueryable result from your repository enables calling code to append on chained operator methods to it, and so participate in the ultimate query execution. If you do not want to provide calling code this ability, then you should return back IList or IEnumerable results - which contain the results of a query that has already executed.
For pagination scenarios this would require you to push the actual data pagination logic into the repository method being called. In this scenario we might update our FindUpcomingDinners() finder method to have a signature that either returned a PaginatedList:
PaginatedList< Dinner> FindUpcomingDinners(int pageIndex, int pageSize) { }
Or return back an IList, and use a "totalCount" out param to return the total count of Dinners:
IList FindUpcomingDinners(int pageIndex, int pageSize, out int totalCount) { }
精彩评论