Pregunta

Estoy buscando una forma de almacenar una colección de Expression<Func<T, TProperty>> utilizada para ordenar elementos, y luego ejecutar la lista almacenada contra un objeto IQueryable<T> (el proveedor subyacente es Entity Framework).

Por ejemplo, me gustaría hacer algo como esto ( esto es pseudo código ):

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);
    }
}

Una orden por cláusula (propiedad sobre la cual ordenar):

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; }
    }
}

Un repositorio con mi método de consulta:

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();
    }
}

Mi primera idea fue convertir la Expression<Func<T, TProperty>> en una cadena (el nombre de la propiedad en la que se ordenará). Básicamente, en lugar de almacenar una lista con tipo (lo que no es posible porque TProperty no es constante), almaceno una lista de cadenas con las propiedades para ordenar.

Pero esto no funciona porque entonces no puedo reconstruir la Expression (lo necesito porque IQueryable.OrderBy toma una Expression<Func<T, TKey>> como parámetro).

También intenté crear dinámicamente la Expresión (con la ayuda de Expression.Convert), para tener una Expression<Func<T, object>> pero luego obtuve una excepción del marco de la entidad que decía que no podía manejar la Expresión. .Convertir declaración.

Si es posible, no quiero usar una biblioteca externa como la biblioteca Dynamic Linq .

Respuesta aceptada

Este es uno de los pocos casos en que una solución dynamic / de reflexión puede ser apropiada.

Creo que quieres algo como esto? (He leído entre líneas e hice algunos cambios en su estructura donde creí que eran necesarios).

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();
    }
}

El truco clave es conseguir que C # dynamic haga la horrible resolución de sobrecarga y la inferencia de tipo para nosotros. Además, creo que lo anterior, a pesar del uso de la dynamic , ¡es en realidad seguro de tipos!


Respuesta popular

Una forma de hacer esto sería "almacenar" todas las cláusulas de ordenación en algo como Func<IQueryable<T>, IOrderedQueryable<T>> (es decir, una función que llama a los métodos de clasificación):

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);
    }
}

De esta manera, no tiene que lidiar con la reflexión o la dynamic , todo este código es seguro y relativamente fácil de entender.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow