Tri LINQ complexe à l'aide d'expressions lambda

.net c# expression-trees linq

Question

Est-ce que quelqu'un a / connaît l'existence d'une extension IQueryable.OrderBy prenant une expression (récupérée, par exemple, par réflexion)? Je crois que la fonction ressemblerait à quelque chose comme ceci:

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

L'expression sera supposée être une Expression<Func<TEntity, T>> où TEntity est le même objet sur lequel le tri est effectué et T est un type qui doit être déterminé pour créer le nouvel IQueryable.

J'ai trouvé de nombreux exemples d'extensions prenant une chaîne, y compris Dynamic Linq, comme ceci:

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

S'il est possible de prendre la chaîne et d'utiliser Reflection pour rechercher le type à partir de l'objet en question, il devrait également être possible de prendre l'expression, et d'obtenir le type de valeur qui se trouve exactement là dans l'expression.


Vous trouverez ci-dessous une explication détaillée des raisons pour lesquelles j'aimerais avoir ce document, dont vous pouvez ou non avoir besoin.

J'ai une assez grande liste d'enregistrements complexes à trier. Comme la liste est trop longue, je préfère que le tri soit effectué côté base de données. Pour gérer des propriétés plus complexes, j'ai créé des expressions qui fournissent la fonctionnalité de tri, comme suit:

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 récupère une expression statique à partir de l'objet qui permet d'effectuer un tri sans opps qui sont toujours du type 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);
        }
    }

Le tri des propriétés simples est effectué à l'aide de méthodes d'extension qui utilisent Reflection pour créer une expression basée sur le nom de la propriété transmise sous forme de chaîne.

Cela fonctionne bien, mais pour les types complexes, j'ai toujours besoin d'une logique de branchement, et je préférerais ne pas le faire. Ce que je préfère faire, c'est rechercher une propriété statique contenant l'expression, puis l'appliquer simplement. Je peux avoir l'expression comme ceci:

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

Pour la propriété PlannedValue, cela me donne l'expression triée dans PlannedValueSorter, que je sais déjà qui fonctionne.


Mettre à jour:

Divers travaux de fouille m'ont permis d'obtenir ce qui, à mon avis, pourrait être un progrès:

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 est en fait le type de retour de l'expression (pour cette propriété particulière, c'est décimal).

Malheureusement, je ne fais que des essais et des erreurs, car je n'ai pas encore compris comment tout cela fonctionne. Ma tentative de mettre à jour la requête comme ça ne fonctionne pas:

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

Il compile bien, mais l'erreur suivante est renvoyée à l'exécution:

Aucune méthode générique 'OrderBy' sur le type 'System.Linq.Queryable' n'est compatible avec les arguments de type fournis. Aucun argument de type ne doit être fourni si la méthode est non générique.

Réponse populaire

Ok, j'ai une solution:

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

La valeur décroissante consiste à permettre les surcharges standard OrderBy et OrderByDescending. Deux percées majeures ici, du moins pour moi:

  1. Sortir l'opérande de l'expression.
  2. Utiliser Expression.Call et Expression.Lambda pour créer la nouvelle expression me permet d'utiliser une variable "Type" réelle, alors que la syntaxe Expression<Func<TEntity, T>> nécessite l'utilisation d'un type connu au moment de la compilation.


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow