3

I want to implement sorting objects by selected property.

            Expression<Func<Goal, object>> sortProperty;

            switch (sc.Sort)
            {
                case "Name":
                    sortProperty = p => p.Name; // String
                    break;
                case "Priority":
                    sortProperty = p => p.Priority; // Enum
                    break;

                case "CreatedDate":
                    sortProperty = p => p.CreatedDate; // DateTime
                    break;

                case "Id":
                default:
                    sortProperty = p => p.Id; // Long
                    break;
             }

             queryable = sc.IsAscending
                 ? queryable.OrderBy(sortProperty)
                 : queryable.OrderByDescending(sortProperty);

When I materialize query by ToList method I get exception (in 'Id' scenario):

Additional information: Unable to cast the type 'System.Int64' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.

My question is: is it possible to implement feature in such way? Or I should move chooseing between OrderBy and OrderByDescending to each case?

Jacek
  • 10,507
  • 21
  • 58
  • 108
  • Why do you need that to `(object)` cast in `sortProperty = p => (object)p.Id; // Long`? What is wrong with plain `sortProperty = p => p.Id;`? – Eugene Podskal Jan 07 '17 at 20:08
  • @EugenePodskal: That was my first thought too but `sortProperty` is of type `Expression>` which means I suspect it has the same complaint broadly without the cast (not actually tried the code to see the exact complaint). – Chris Jan 07 '17 at 20:09
  • This (object) cast is from my testing. I remove it from code – Jacek Jan 07 '17 at 20:10
  • I assume the sorting has to be done on the queryable rather than being done after (ie you are using paging or somethign that needs them to be ordered in the database)? – Chris Jan 07 '17 at 20:13
  • Dynamic sorting has been "sorted out" long ago: http://stackoverflow.com/a/233505/861716 – Gert Arnold Jan 08 '17 at 10:04

1 Answers1

1

This happens because of boxing. Converting string to object is a no-op, removed before EF sees it, which is why it would work when sorting by name. Converting long to object does quite a bit of work, which is why it is preserved in the expression tree, and EF gets confused by it.

You need to find a way to use Expression<Func<Goal, long>> in that case. Helper methods are probably the easiest way to avoid code duplication.

abstract class Orderer {
    public abstract IQueryable<Goal> Order(IQueryable<Goal> queryable);
}

class Orderer<T> : Orderer {
    private readonly Expression<Func<Goal, T>> _property;
    public Orderer(Expression<Func<Goal, T>> property) {
        _property = property;
    }
    public override IQueryable<Goal> Order(IQueryable<Goal> queryable) {
        return queryable.OrderBy(_property);
    }
    public override IQueryable<Goal> OrderDescending(IQueryable<Goal> queryable) {
        return queryable.OrderByDescending(_property);
    }
}

Orderer makeOrderer<T>(Expression<Func<Goal, T>> property) {
    return new Orderer(property);
}

and then

Orderer orderer;
switch (sc.Sort)
{
    case "Name":
        orderer = makeOrderer(p => p.Name);
        break;
    // ...
}
queryable = sc.IsAscending
    ? orderer.Order(queryable)
    : orderer.OrderDescending(queryable);