Clasificación LINQ compleja utilizando expresiones Lambda

.net c# expression-trees linq

Pregunta

¿Alguien tiene / sabe de una extensión IQueryable.OrderBy que toma una Expresión (recuperada, por ejemplo, por Reflexión)? Creo que la función se vería así:

public static IQueryable<TEntity> OrderBy<TEntity>
    (this IQueryable<TEntity> source, Expression sortExpression)

Se asumirá que la Expression<Func<TEntity, T>> es una Expression<Func<TEntity, T>> donde TEntity es el mismo objeto que se está ordenando, y T es un tipo que debe determinarse para crear el nuevo IQueryable.

He encontrado muchos ejemplos de extensiones que toman una cadena, incluyendo Dynamic Linq, como esto:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, string sortExpression) 

Si es posible tomar la cadena y usar la Reflexión para buscar el tipo del objeto en cuestión, también debería ser posible tomar la Expresión y obtener el tipo de valor que está justo ahí EN LA Expresión.


La siguiente es una explicación detallada de por qué me gustaría tener esto, que puede o no necesitar.

Tengo una lista bastante grande de registros complejos para ordenar. Debido a que la lista es muy larga, prefiero que la clasificación se realice en el lado de la base de datos. Para manejar propiedades más complejas, he creado Expresiones que proporcionan la funcionalidad de clasificación, de esta manera:

if (model.sortExpression == "PlannedValue")
{
    Expression<Func<BDopp, decimal>> sorter = BDopp.PlannedValueSorter;         
    if (model.sortDirection == "DESC")
        opps = opps.OrderByDescending(sorter).AsQueryable();
    else
        opps = opps.OrderBy(sorter).AsQueryable();
}

BDOpp.PlannedValueSorter recupera una expresión estática del objeto que permite que la clasificación se realice sin que los opps sigan siendo de tipo IQueryable:

public static Expression<Func<BDopp, decimal>> PlannedValueSorter
    {
        get
        {
            return z => z.BudgetSchedules
                .Where(s => s.Type == 1)
                .Sum(s => s.Value * s.Workshare * z.valueFactor / 100 / 100);
        }
    }

La clasificación de propiedades simples se realiza con métodos de Extensión que usan Reflection para construir una expresión basada en el nombre de la propiedad pasada como una cadena.

Esto funciona bien, pero para los tipos complejos, todavía necesito lógica de ramificación, y prefiero no hacerlo. Lo que preferiría hacer es buscar una propiedad estática que contenga la expresión y luego aplicarla. Puedo obtener la expresión como esta:

PropertyInfo info = typeof(BDopp).GetProperty(model.sortExpression + "Sorter",
    BindingFlags.Static | BindingFlags.Public);
Expression expr = (Expression)info.GetValue(null, null);

Para la propiedad PlannedValue, esto me da la expresión ordenada en PlannedValueSorter, que ya sé que funciona.


Actualizar:

Varios trabajos de excavación me han dado lo que creo que podría ser algún progreso:

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression)
    {
        var unary = sortExpression.Body as UnaryExpression;
        Type actualExpressionType = unary.Operand.Type;

actualExpressionType es de hecho el tipo de retorno de la expresión (para esta propiedad en particular, es decimal).

Desafortunadamente, la mayoría de las veces solo trabajo por prueba y error, ya que aún no he envuelto mi cerebro en torno a cómo funciona todo esto, por lo que mi intento de actualizar la consulta no funciona:

        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), 
            "OrderBy",
            new Type[] { typeof(TEntity), actualExpressionType },
            source.Expression, sortExpression);
        return source.Provider.CreateQuery<TEntity>(resultExp);

Se compila bien, pero el siguiente error se produce en tiempo de ejecución:

Ningún método genérico 'OrderBy' en el tipo 'System.Linq.Queryable' es compatible con los argumentos y argumentos de tipo suministrados. No se deben proporcionar argumentos de tipo si el método no es genérico.

Respuesta popular

Está bien, tengo una solución:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression, 
    bool descending)
    {
        var unary = sortExpression.Body as UnaryExpression;
        var operand = unary.Operand;
        Type actualExpressionType = operand.Type;

        MethodCallExpression resultExp = 
            Expression.Call(typeof(Queryable), 
                descending? "OrderByDescending" : "OrderBy",
                new Type[] { typeof(TEntity), actualExpressionType },
                source.Expression, 
                Expression.Lambda(operand, sortExpression.Parameters));
        return source.Provider.CreateQuery<TEntity>(resultExp);
    }

El bool descendente es permitir las sobrecargas estándar de OrderBy y OrderByDescending. Dos grandes avances aquí, al menos para mí:

  1. Obtención del operando de la expresión.
  2. Uso de Expression.Call y Expression.Lambda para crear la nueva expresión: esto me permite usar una variable "Tipo" real, mientras que la sintaxis de la Expression<Func<TEntity, T>> requiere que uses un tipo que se conoce en el momento de la compilación.


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