3

right now I am returning a list in my model to the view. The list contains 300k items.

I am doing something like this

<table>
        <thead>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>E-mail</th>
                <th>Role</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var user in Model) {
                <tr>
                    <td>@user.FirstName</td>
                    <td>@user.LastName</td>
                    <td>@user.Email</td>
                    <td>@user.UserRole.UserRoleName</td>
                </tr>
            }
        </tbody>
    </table>

So ye problem is that it takes forever to load the page to display the 300k items, plus all the scrolling is a problem. Is there a nicer solution in MVC to handle this and add automatic pagination etc ?

tereško
  • 58,060
  • 25
  • 98
  • 150
StevieB
  • 6,263
  • 38
  • 108
  • 193

3 Answers3

4

There are many solutions out there that could help you but the tricky part is that none will solve it completely.

What I'm saying is that they will help with the client-side, with the pagination component and grid to display data or even with an endless scroll (like facebook timelike, which loads N more recods, on demand). Some will, also, help you on server-side.

But since it's too related to your data-source and data-structure, there's no "valid-for-all-cases" solution.

I have about 10 production-stage large-scale ASP.NET MVC projects, not every one of them with the same technologies. Most of them use the jQuery DataTables, which is a consolidated jquery plugin to help you with many things, including sort, filter and, of course, server-side pagination. On the very end of my answer you'll find some links to both DataTables and to my project on GitHub which helps with server-side, along with some other interesting links.

Recently we've moved towards Angular (from Google team). It has a built-in grid mechanism (you can use others, if you like) called ngGrid, which also enables pagination, filtering and sorting.

Consider the code bellow as an "action-template", which should help you on getting started with server-side pagination (I'm assuming you're using EF for the sake of this sample):

public JsonResult Paginate(int page, int recordsPerPage, string filter, string[] sorting)
{
   // 1. count total (non-filtered) data from your database.
   var _total = context.Entities.Count();

   // 2. count filtered (available) record set
   var _filtered = context.Entities.Where(_e => _e.Description.ToLower().Contains(filter)).Count();

   // 3. retrieve only desired page, here follows some linq sample if you're using EF
   // I'm assuming your page count starts with 1 (natural counting), that's why I'm subtracting
   // one from the current page number
   var _page = context.Entities.Where(_e => _e.Description.ToLower().Contains(filter)).OrderBy(_e => _e.Description).Skip((page - 1) * recordsPerPage).Take(recordsPerPage);

   // 4. Return a json object (or view result with all counters and data to display)
   return Json(new { TotalRecords = total, FilteredRecords = filtered, Records = _page });
}

Bear in mind that for the sake of easiness here, I'm not processing the sort collection. You should do that, of course, so your end-user may sort all data in an easy way.

Now, from your client-side, you'll have requests (ajax, I'd suggest, to fetch only necessary data if possible) in a way similar to:

<script>
   $.ajax({
      url: "/my/route",
      data: { page: _page, recordsPerPage: _recordsPerPage, filter: _filter, sorting: _sorting },
      method: "POST",

      success: function(jsonResult) { /* update your data on grid/table/div/etc */ },

      error: function(errorResult) { console.log(errorResult); /* temporary, so you may find out what happened*/ }
   });
</script>

Please, consider that _page is a variable where you have the "next-desired-page", which could be the first, prior, next, last page or a specific one (most common cases), _recordsPerPage is a variable that might change when the user selects a larger (or smaller) page-size from a dropdown, _filter is a string which get's updated on the blur event of a text-input and _sorting is a string array where your store desired sorting.

USUALLY plugins (like DataTables, jqGrid and others) will populate these variables for you or provide you with methods to get these variables.

As a general rule of usability, NEVER return more than 20 records for the end-user unless he/she explicitly says otherwise (eg: by selecting a records-per-page value from a dropdown). You'll avoid unecessary loads from your database, unecessary link consumptions and your pages will load much much faster.

Another suggestion I'd make is to create a helper class for your pagination so you'll always have a standard, like:

public class PageViewModel<TEntity>
{
   public int TotalCount { get; private set; }
   public int FilteredCount { get; private set; }
   public IEnumerable<TEntity> Records { get; private set; }
   public int RecordsPerPage { get; private set; }

   // Help with some jQuery libs to help counting the number of pages to display...
   public int AvailablePages { get; private set; }

   /* Constructor */
}

This way you may create some sort of generic partial view to display data regardless of the type of entity returned by your page.

But all that might change depending on what you're using. For instance, if your using pure ADO.NET, you'll have to handle diferent SQL syntax for the same SKIP/TAKE method and that implementation might change from DB version. Pre-2008 MSSQL will not help you with pagination.

If you're using Dapper (which is the ORM under StackOverflow), you can either write your pagination SQL or make use of an extention, like DapperExtensions, Rainbow or Dapper.Contrib which will help with with fancy methods for pagination without all that SQL syntax stuff.

If you're into EF, you'll simply use LinqToEntities and it's all set. From what I know about NHibernate (never actually used), should be similar with LinqToNHibernate.

Also, that might change if you're using (or not) repositories. Of you are, your controller might simply delegate page resolve and record fetch to your repository in a centralized way. If not, you'll end up with either lots of duplicate code or lots of helpers everywhere.

But if you're not into javascript, you can make all of it work with regular ASP.NET MVC. You'll have more work but yes, it's possible.

Anyway, here are some links to help you:

Anderson Matos
  • 3,132
  • 1
  • 23
  • 33
0

I find when the data set gets too large it is better for both the backend and frontend to only grab the data I need to render a subset of the total data set. In LINQ, it would be a .Skip .Take to get the paginated data you need.

0

While the answer provided by @marcusking2002 is correct, there are a number of well featured jQuery grid components out there that would give your users a richer user experience and save you a lot of manual formatting.

These include jqGrid http://www.trirand.com/blog/ , flexigrid http://www.flexigrid.info/ (for a free option), or if you want to stump up some cash then Kendo's offering http://www.telerik.com/kendo-ui is very powerful.

There are others, these are simply ones that I've used :)

Jane S
  • 1,417
  • 16
  • 21