Suggestion for change of interface around sorting (custom sorting/custom ordering)

Apr 12, 2014 at 9:02 PM
Hi guys, love the work put into the grid. Works for most scenarios so far.

I found one issue around the public interface of the component regarding column sorting and display of navigational properties of complex type, where they can optionally be null.

Below a description of how I got passed it (for feedback if there is a simpler way?) and if not, for others to follow.

I have an entity (Ticket) with an optional "Owner". I display the Owner.Username in the grid. And since the system is a ticket-management system, sorting and filtering on Owner.Username is essential functionality for the grid.

Initially, my test-data didn't contain empty owners, so I didn't notice this until i started running with real data. And then owner is initially always NULL. Before hitting the issue my column configuration was like this:
columns.Add(ticket => ticket.Owner.Username).Titled("Owner").Sortable(true).Filterable(true).Css("hidden-xs");
After hitting the problem, i figured that i only need to remove the "Username" and implement the cell rendering with a conditional check for NULL.
columns.Add(ticket => ticket.Owner).Titled("Owner").Sortable(true).Filterable(true).Css("hidden-xs")
    .RenderValueAs(d => d.Owner == null ? string.Empty : d.Owner.Username);
Then i hit problems with sorting instead. This is an EF model, so ordering needs to be on a proper column, and the expression added was on the object itself now (d.Owner). So then I tried adding the column without any expressions etc, but then you don't get the sorting either, and there is no way to specify custom sorting.

So I digged in and figured out that with some work, i could get the columns and work on them. So this is the code I ended up with:
GridColumn<Ticket, User> col = (GridColumn<Ticket, User>)columns.Add(ticket => ticket.Owner).Titled("Owner").Sortable(true).Filterable(true).Css("hidden-xs")
    .RenderValueAs(d => d.Owner == null ? string.Empty : d.Owner.Username);
((List<IColumnOrderer<Ticket>>)col.Orderers).Clear();
((List<IColumnOrderer<Ticket>>)col.Orderers).Insert(0, new TicketOrderByGridOrderer<Ticket, string>(ticket => ticket.Owner.Username));
In addition to that code, i also had to implement a new IColumnOrderer<T> because the default one is marked internal. Notice that I also do a lot of downcasting from the interfaces returned by the original API. That code is not included since it was just a copy of the internal class OrderByGridOrderer<T, TKey>

So, my suggestion is that you expose the API for everything related to implementing a custom sorter in the way I just did. Preferable the experience I would want is the following:
columns.Add(ticket => ticket.Owner.Username).Titled("Owner").Sortable(true).Filterable(true).Css("hidden-xs")
    .RenderValueAs(d => d.Owner == null ? string.Empty : d.Owner.Username);
And Mvc.Grid should deal with the rendering issue caused by a NULL Owner. But the power would really come from having the possability to do this:
columns.Add(ticket => ticket.Owner).Titled("Owner").Sortable(true).Filterable(true).Css("hidden-xs")
    .RenderValueAs(d => d.Owner == null ? string.Empty : d.Owner.Username);
    .SortAs(s => s.Owner.Username);
YES, I do realize that I should implement a proper view-model for the view, but guys, I'm lazy (I believe lazyness is an important skill of any good coder). I also don't think that will work propely with paging without a lot of extra implementation of custom paging in the controller. So, this was the simpliest way I could solve it right now...

Keep up the good work. And if there is a better/easier way .. I am always open for ways to further improve on my lazyness skills :-)

Cheers,
Niclas
Coordinator
Apr 16, 2014 at 4:05 AM
Hi,
Hmm...

I try to reproduce your scenario on the demo app (https://gridmvc.codeplex.com/SourceControl/latest) and I have no problems with sorting. I made some orders with NULL customers and use:
        columns.Add(o => o.Customer.CompanyName)
               .Titled("Company")
               .SetWidth(250)
               .ThenSortByDescending(o => o.OrderID)
               .RenderValueAs(o => o.Customer == null ? string.Empty : o.Customer.CompanyName)
If you are pass IQueryable<T> collection, generated by EF, grid will built query expressions like .OrderBy( order => order.Customer.CompanyName ) and it will correctly translated to SQL queries. The problem may appears if grid will use LINQ to Objects to filter the initial collection...
Marked as answer by gurun on 4/16/2014 at 9:46 AM
Apr 16, 2014 at 4:45 PM
Yeah, you know. It works just fine. As always, i could have sworn that I tried all combinations of this, but if you look at my example I am missing the exact one you pointed out. It could be true about the LINQ too, because all that code have changed since, and now it works.

So the missing example i had was this (and this works as expected as you pointed out):
                    columns.Add(ticket => ticket.Owner.UserName).Titled("Owner").Sortable(true)
                        .RenderValueAs(d => d.Owner == null ? string.Empty : d.Owner.UserName);
Thanks for taking the time to waste it on that.. my problem is solved, but there is no cure for stupidity.

Thanks,
Niclas