Search code examples
entity-frameworklinq-to-entities

LINQ to Entities - cannot cast 'System.DateTime' to type 'System.Object' in orderBy


I am trying to order an IQueryable of entities by date from a passed in Expression< Func< T, object>> and am getting the error: "Unable to cast the type 'System.Nullable`1' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types." The entity has a nullable datetime property on it on which I am trying to sort:

Example: (where e.Date is a nullable DateTime)

Expression<Func<T,object>> sorter = (e) => e.Date;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);

Thanks in advance!


Solution

  • I wrote a simple class for ordering entities based on a lambda expression at runtime.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace DataModeling
    {
        public class QueryOrderer<TEntity>
            where TEntity : class
        {
            private LambdaExpression defaultSortExpression;
            private Dictionary<string, LambdaExpression> orderFieldLookup;
    
            public QueryOrderer()
            {
                orderFieldLookup = new Dictionary<string, LambdaExpression>();
            }
    
            public void AddOrderMapping<TProp>(string fieldName, Expression<Func<TEntity, TProp>> selector)
            {
                orderFieldLookup[fieldName] = selector;
            }
    
            public void SetDefaultSortExpression<TProp>(Expression<Func<TEntity, TProp>> selector)
            {
                defaultSortExpression = selector;
            }
    
            public IQueryable<TEntity> GetOrderedEntries(string field, bool isDescending, IQueryable<TEntity> entries)
            {
                return orderEntries(entries, field, isDescending);
            }
    
            private IQueryable<TEntity> orderEntries(IQueryable<TEntity> entries, string fieldName, bool isDescending)
            {
                dynamic lambda = getOrderByLambda(entries, fieldName);
                if (lambda == null)
                {
                    return entries;
                }
                if (isDescending)
                {
                    return Queryable.OrderByDescending(entries, lambda);
                }
                else
                {
                    return Queryable.OrderBy(entries, lambda);
                }
            }
    
            private dynamic getOrderByLambda(IQueryable<TEntity> entries, string fieldName)
            {
                if (!String.IsNullOrWhiteSpace(fieldName) && orderFieldLookup.ContainsKey(fieldName))
                {
                    return orderFieldLookup[fieldName];
                }
                else
                {
                    return defaultSortExpression;
                }
            }
        }
    }
    

    You use this class by initially setting up all of the fields:

    QueryOrderer<User> orderer = new QueryOrderer<User>();
    orderer.SetDefaultSortExpression(u => u.FullName);
    orderer.AddOrderMapping("UserId", u => u.UserId);
    orderer.AddOrderMapping("Name", u => u.FullName);
    orderer.AddOrderMapping("Email", u => u.Email);
    orderer.AddOrderMapping("CreatedOn", u => u.CreatedOn);
    
    ...
    
    var users = orderer.GetOrderedEntries("CreatedOn", isDescending: false, context.Users);
    

    I nice feature of this code is that it handles look-up values perfectly. For instance, if you're trying to sort using the description rather than a key, you can use the outer context when building up the sort expression:

    orderer.AddOrderMapping("UserType", 
        u => context.UserTypes
                    .Where(t => t.UserTypeId == u.UserTypeId)
                    .Select(t => t.Description)
                    .FirstOrDefault());
    

    Entity Framework is smart enough to just fold the sub-query right into the outer query.