Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 466
Bidou Avatar asked May 21 '13 19:05

Bidou


3 Answers

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!

like image 89
Ani Avatar answered Oct 17 '22 08:10

Ani


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.

like image 30
svick Avatar answered Oct 17 '22 06:10

svick


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();
    }
}
like image 39
Gebb Avatar answered Oct 17 '22 08:10

Gebb