List of Expression<Func<T, TProperty>>

13,213

Solution 1

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!

Solution 2

One way to do this would be to “store” all the sort clauses in something like Func<IQueryable<T>, IOrderedQueryable<T>> (that is, a function that calls the sorting methods):

public class OrderClause<T>
{
    private Func<IQueryable<T>, IOrderedQueryable<T>> m_orderingFunction;

    public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
    {
        if (m_orderingFunction == null)
        {
            m_orderingFunction = q => q.OrderBy(orderBySelector);
        }
        else
        {
            // required so that m_orderingFunction doesn't reference itself
            var orderingFunction = m_orderingFunction;
            m_orderingFunction = q => orderingFunction(q).ThenBy(orderBySelector);
        }
    }

    public IQueryable<T> Order(IQueryable<T> source)
    {
        if (m_orderingFunction == null)
            return source;

        return m_orderingFunction(source);
    }
}

This way, you don't have to deal with reflection or dynamic, all this code is type safe and relatively easy to understand.

Solution 3

You can store your lambda expressions in a collection as instances of the LambdaExpression type.

Or even better, store sort definitions, each of which, in addition to an expression, aslo stores a sorting direction.

Suppose you have the following extension method

public static IQueryable<T> OrderBy<T>(
    this IQueryable<T> source,
    SortDefinition sortDefinition) where T : class
{
    MethodInfo method;
    Type sortKeyType = sortDefinition.Expression.ReturnType;
    if (sortDefinition.Direction == SortDirection.Ascending)
    {
        method = MethodHelper.OrderBy.MakeGenericMethod(
            typeof(T),
            sortKeyType);
    }
    else
    {
        method = MethodHelper.OrderByDescending.MakeGenericMethod(
            typeof(T),
            sortKeyType);
    }

    var result = (IQueryable<T>)method.Invoke(
        null,
        new object[] { source, sortDefinition.Expression });
    return result;
}

and a similar method for ThenBy. Then you can do something like

myQueryable = myQueryable.OrderBy(sortDefinitions.First());

myQueryable = sortDefinitions.Skip(1).Aggregate(
   myQueryable,
   (current, sortDefinition) => current.ThenBy(sortDefinition));

Here are the definitions of SortDefinition and MethodHelper

public class SortDefinition
{
    public SortDirection Direction
    {
        get;
        set;
    }

    public LambdaExpression Expression
    {
        get;
        set;
    }
}

internal static class MethodHelper
{
    static MethodHelper()
    {
        OrderBy = GetOrderByMethod();
        ThenBy = GetThenByMethod();
        OrderByDescending = GetOrderByDescendingMethod();
        ThenByDescending = GetThenByDescendingMethod();
    }

    public static MethodInfo OrderBy
    {
        get;
        private set;
    }

    public static MethodInfo ThenBy
    {
        get;
        private set;
    }

    public static MethodInfo OrderByDescending
    {
        get;
        private set;
    }

    public static MethodInfo ThenByDescending
    {
        get;
        private set;
    }

    private static MethodInfo GetOrderByMethod()
    {
        Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr =
            q => q.OrderBy((Expression<Func<object, object>>)null);

        return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
    }

    private static MethodInfo GetThenByMethod()
    {
        Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr =
            q => q.ThenBy((Expression<Func<object, object>>)null);

        return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
    }

    private static MethodInfo GetOrderByDescendingMethod()
    {
        Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr =
            q => q.OrderByDescending((Expression<Func<object, object>>)null);

        return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
    }

    private static MethodInfo GetThenByDescendingMethod()
    {
        Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr =
            q => q.ThenByDescending((Expression<Func<object, object>>)null);

        return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
    }
}
Share:
13,213
Bidou
Author by

Bidou

Updated on June 03, 2022

Comments

  • Bidou
    Bidou almost 2 years

    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.

  • Bidou
    Bidou almost 11 years
    Yes that's true, it's quite easy, but the problem with this solution is that you need a direct access to IQueryable. My list of OrderBy is intended to be used by the GUI (basically to abstract the query), so I don't want to have a Queryable object here... I would prefer to manage it in the Business Logic !
  • Bidou
    Bidou almost 11 years
    That works great, thanks! However, the aggregation will probably not work (I didn't test it) since you need to call ThenBy/ThenByDescending if you want to do multiple sort...
  • Bidou
    Bidou almost 11 years
    Something else: to do the query, using the dynamic keywork as proposed by Ani is easier. Basically, it replaces your MethodHelper class. But thanks anyway for your helpful answer!
  • svick
    svick almost 11 years
    @Bidou I really don't understand what you're trying to say. If you want to keep Repository like in your pseudo code, just change its code to take the whole OrderClause and then do return orderBys.Order(_query).ToList();.
  • Ani
    Ani almost 11 years
    +1: This is a really nice solution and as you say, neatly dodges dynamic / reflection.
  • Gebb
    Gebb almost 11 years
    @Bidou: I tested it and found that aggregation with OrderBy doesn't work. Will edit the answer.
  • Gebb
    Gebb almost 11 years
    A nice solution indeed! It's probably harder to debug though, compared to the dynamic solution. I mean, there's no easy way to inspect the individual components (sort expressions) of an OrderClause, you have to apply it to an IQueryable to see what's inside.
  • Bidou
    Bidou almost 11 years
    @svick It's a bit difficult to explain everything in just a few words... But in fact, OrderClause<T> is more a QueryOption<T> where I have OrderBy as well as other things like Where predicate, paging, ... The goal of this is to offer to the GUI the possibility to create a query without knowing how it works in the Business Logic (QueryOption<T> works with a DTO, for example QueryOption<UserDTO> but my Repository<T> with an entity object, for example QueytionOption<User>. That means that I'm doing a mapping somewhere in between...)
  • Bidou
    Bidou almost 11 years
    Thus, I think that it's not a good idea for this QueryOption<T> to have a reference on IQueryable<T>, isn't it?
  • svick
    svick almost 11 years
    @Bidou I don't see why would a dependence on IQueryable<T> be bad. What else is someone else going to use? But I have no idea how are you doing the mapping., which might change things.
  • Bidou
    Bidou almost 11 years
    For example because having a direct access to IQueryable<T> in the GUI gives the possibility to navigate through the whole EF graph with navigation properties (in my case, the IQueryable<T>'s provider is Entity Framework) which will bypass all my security logic! This is a reason why I choose a DTO pattern and why I don't want to have IQueryable in this QueryOption<T>. Am I wrong?
  • svick
    svick almost 11 years
    @Bidou I don't know what “security logic” are you talking about, but I think it doesn't make much sense trying to protect against malicious code in the GUI layer, if that's what you meant.