Question

I'm playing around and trying to make an extension method for IQueryable that sorts it by an arbitrary property of a object.

public static class IQueryableExtender

{

public static IQueryable<TSource> Sort<TSource>(this IQueryable<TSource> query, string orderProperty, string sortDirection = "asc")      
{

    var elementType = query.ElementType;

    var propertyInfo = elementType.GetProperty(orderProperty);
    if (propertyInfo == null)
    {
         throw new ArgumentException(string.Format("{0} is not a property on {1}", orderProperty, elementType.Name));
    }


    switch (sortDirection.ToLower())
    {
       case "asc":
       case "ascending":
           return query.OrderBy(x => propertyInfo.GetValue(x, null));
           break;
       case "desc":
       case "descending":
           return query.OrderByDescending(x => propertyInfo.GetValue(x, null));
           break;
    }

    return query;
}
}

I get this error when a call the method. I guess it has to do with the IQueryable hasn't executed and retrieved any objects yet.

LINQ to Entities does not recognize the method System.Object GetValue(System.Object, System.Object[]), and this method cannot be translated into a store expression.

I can solve it by executing ToList on the IQueryable but then I retrieve the data in the extension method and that is not what I want.

Can this be solved?

Was it helpful?

Solution

LINQ to Entities tries to run your queries in the database when you do a LINQ operation on an IQueryable<>. In your case "asc", you do query.OrderBy, which LINQ to Entities interprets as "transform this to SQL", and it fails since you use reflection calls, which it doesn't know how to convert to SQL.

You could do query.AsEnumerable().OrderBy(...). One effect of this is that as the OrderBy operation starts running, the rest of the query will execute in order for the data to be provided.

Rather than use these reflection tricks, you could get by simply by using the OrderBy and OrderByDescending methods, designed to take a delegate to pull the sort value out. (items.OrderBy(item => item.Property)). What you're missing is the ability to specify ascending or descending within the same method, but I would simply do a pair of methods like:

public static IOrderedQueryable<TSource> OrderByAscDesc<TSource, TKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TKey>> keySelector, bool isAsc
) {
    return (isAsc ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
}

public static IOrderedQueryable<TSource> OrderByAscDesc<TSource, TKey>(
    this IQueryable<TSource> source,
    Func<TSource, TKey> keySelector, bool ascDesc
) {
    return (isDesc ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top