mmelear
mmelear

Reputation: 97

Dynamic Linq queries with sorting, nulls at end

I am trying to build an ASP.NET page that sorts on nullable fields. What I want to do is, if there are null values in the sortable field, always throw those rows to the end of the list. Otherwise, they show up at the beginning of the list, and I can imagine pages and pages of null valued rows before you get to the A-Z sorted elements.

I am working in an ASP.NET framework. The solution for sortable columns is to build a dynamic query against the GridViewDataSource found here:

public static class QueryExtensions {
public static IQueryable<T> SortBy<T>(this IQueryable<T> source, string propertyName) {
    if (source == null) {
        throw new ArgumentNullException("source");
    }
    // DataSource control passes the sort parameter with a direction
    // if the direction is descending          
    int descIndex = propertyName.IndexOf(" DESC");
    if (descIndex >= 0) {
        propertyName = propertyName.Substring(0, descIndex).Trim();
    }

    if (String.IsNullOrEmpty(propertyName)) {
        return source;
    }

    ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
    MemberExpression property = Expression.Property(parameter, propertyName);
    LambdaExpression lambda = Expression.Lambda(property, parameter);

    string methodName = (descIndex < 0) ? "OrderBy" : "OrderByDescending";

    Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
                                        new Type[] { source.ElementType, property.Type },
                                        source.Expression, Expression.Quote(lambda));

    return source.Provider.CreateQuery<T>(methodCallExpression);
}

}

Because I am working within this framework, I am not going to change the way that this function works. Instead, I am trying to overload the method with this signature:

public static IQueryable<T> SortBy<T>(this IQueryable<T> source, string propertyName, bool nullsToEnd)

The problem I am having is that I cannot figure out how to edit the 'methodName' to specifically allow for this type of sorting (A-Z with nulls at the end) to happen.

Any ideas as to what would do the trick?

Thanks

Upvotes: 0

Views: 592

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70652

I don't think there's a method name per se that will do what you want. Instead, you'll need to select a different method overload in this scenario, one that takes a custom IComparer instance. Then you need to implement an IComparer that wraps Comparer.Default, returning that comparer's result when nulls are not involved, and sorting nulls to the end when they are.

Note that if you want nulls at the end for both the OrderBy and OrderByDescending cases, you'll need to adjust your IComparer implementation accordingly. Either have it handle both the nulls and the ascending/descending option, or have it sort the nulls to the beginning when doing the "descending" case (personally, since you have to adjust the IComparer either way based on the ascending/descending state, I would go ahead and just have the IComparer handle the ascending/descending part as well).

For example:

class NullsAtEndComparer<T> : IComparer<T> where T : class
{
    private static readonly IComparer<T> _baseComparer = Comparer<T>.Default;

    private readonly bool _ascending;

    public NullsAtEndComparer(bool ascending = true)
    {
        _ascending = ascending;
    }

    public int Compare(T t1, T t2)
    {
        if (object.ReferenceEquals(t1, t2))
        {
            return 0;
        }

        if (t1 == null)
        {
            return 1;
        }

        if (t2 == null)
        {
            return -1;
        }

        return _ascending ? _baseComparer.Compare(t1, t2) : _baseComparer.Compare(t2, t1);
    }
}

Then for the "nulls at end" scenario, just always use the OrderBy method and supply an instance of the above IComparer implementation for that method.

Upvotes: 2

Related Questions