Search code examples
c#.netentity-frameworklambdaexpression-trees

List of Expression<Func<T, TProperty>>


I'm searching a way to store a collection of Expression<Func<T, TProperty>> used to order elements, and then to execute the stored list against a IQueryable<T> object (the underlying provider is Entity Framework).

For example, I would like to do something like this (this is pseudo code):

public class Program
{
    public static void Main(string[] args)
    {
        OrderClause<User> orderBys = new OrderClause<User>();
        orderBys.AddOrderBy(u => u.Firstname);
        orderBys.AddOrderBy(u => u.Lastname);
        orderBys.AddOrderBy(u => u.Age);

        Repository<User> userRepository = new Repository<User>();
        IEnumerable<User> result = userRepository.Query(orderBys.OrderByClauses);
    }
}

An order by clause (property on which to order):

public class OrderClause<T>
{
    public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
    {
        _list.Add(orderBySelector);
    }

    public IEnumerable<Expression<Func<T, ???>>> OrderByClauses
    {
        get { return _list; }
    }
}

A repository with my query method:

public class Repository<T>
{
    public IEnumerable<T> Query(IEnumerable<OrderClause<T>> clauses)
    {
        foreach (OrderClause<T, ???> clause in clauses)
        {
            _query = _query.OrderBy(clause);
        }

        return _query.ToList();
    }
}

My first idea was to convert the Expression<Func<T, TProperty>> into a string (the property name on which to sort). So basically, instead of storing a typed list (which is not possible because the TProperty is not constant), I store a list of string with the properties to sort on.

But this doesn't work because then I cannot reconstruct the Expression back (I need it because IQueryable.OrderBy takes a Expression<Func<T, TKey>> as parameter).

I also tried to dynamically create the Expression (with the help of Expression.Convert), to have a Expression<Func<T, object>> but then I got an exception from entity framework that said that it was not able to handle the Expression.Convert statement.

If possible, I do not want to use an external library like the Dynamic Linq Library.


Solution

  • This is one of the few cases where a dynamic / reflection solution may be appropriate.

    I think you want something like this? (I've read between the lines and made some changes to your structure where I thought necessary).

    public class OrderClauseList<T>
    {
        private readonly List<LambdaExpression> _list = new List<LambdaExpression>();
    
        public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
        {
            _list.Add(orderBySelector);
        }
    
        public IEnumerable<LambdaExpression> OrderByClauses
        {
            get { return _list; }
        }
    }
    
    public class Repository<T>
    {
        private IQueryable<T> _source = ... // Don't know how this works
    
        public IEnumerable<T> Query(OrderClause<T> clauseList)
        {
            // Needs validation, e.g. null-reference or empty clause-list. 
    
            var clauses = clauseList.OrderByClauses;
    
            IOrderedQueryable<T> result = Queryable.OrderBy(_source, 
                                                            (dynamic)clauses.First());
    
            foreach (var clause in clauses.Skip(1))
            {
                result = Queryable.ThenBy(result, (dynamic)clause);
            }
    
            return result.ToList();
        }
    }
    

    The key trick is getting C# dynamic to do the horrible overload resolution and type-inference for us. What's more, I believe the above, despite the use of dynamic, is actually type-safe!