Renvoie une expression à partir d'une méthode à utiliser dans OrderBy

c# expression-trees lambda linq

Question

Je souhaite écrire une méthode abstraite à remplacer par des classes enfant. OrderBy() méthode renvoie une expression à utiliser ultérieurement dans LINQ OrderBy() . Quelque chose comme ça:

Remarque: Message hérite de la classe Notes et MyModel hérite de MsgModel .

public class Notes
{
    // abstract definition; using object so that I can (I hope) order by int, string, etc.
    public abstract Expression<Func<MsgModel, object>> OrderByField();
    // ...

    private string GetOrderByFieldName()
    {
        // I am not sure how to write this
        // This is my problem 2. Please see below for problem 1 :-(

        var lambda = OrderByField() as LambdaExpression;

        MemberExpression member = lambda.Body as MemberExpression;

        PropertyInfo propInfo = member.Member as PropertyInfo;

        return propInfo.Name;
    }
}

public class Message : Notes
{
    // second type parameter is object because I don't know the type
    // of the orderby field beforehand
    public override Expression<Func<MyModel, object>> OrderByField()
    {
        return m => m.item_no;
    }
}

Maintenant, si j'essaie de commander par cette voie:

public class Notes
{
    // abstract definition; using object so that I can (I hope) order by int, string, etc.
    public abstract Expression<Func<MsgModel, object>> OrderByField();
    // ...

    private string GetOrderByFieldName()
    {
        // I am not sure how to write this
        // This is my problem 2. Please see below for problem 1 :-(

        var lambda = OrderByField() as LambdaExpression;

        MemberExpression member = lambda.Body as MemberExpression;

        PropertyInfo propInfo = member.Member as PropertyInfo;

        return propInfo.Name;
    }
}

public class Message : Notes
{
    // second type parameter is object because I don't know the type
    // of the orderby field beforehand
    public override Expression<Func<MyModel, object>> OrderByField()
    {
        return m => m.item_no;
    }
}

Je reçois cette erreur:

'Impossible de convertir le type' System.Int32 'en' System.Object '. LINQ to Entities ne prend en charge que le transtypage de types de primitive ou d'énumération EDM. '

Je peux d'emblée affirmer que le paramètre type type est la cause du problème. Ainsi, lorsque je modifie le paramètre type en int, cela fonctionne bien tant que je commande par un champ int (par exemple, le champ item_no ).

Q1. Comment puis-je obtenir que cela fonctionne? Bien sûr, je peux utiliser une propriété de chaîne OrderByField au lieu de cette méthode de renvoi d’expression et d’obtenir sa commande, probablement en écrivant une méthode d’extension pour IQueryable (peut-être en utilisant cette excellente réponse ). Mais je veux avoir plus d’intellisense tout en passant l’ordre.

Q2. Comment puis-je obtenir le nom de la colonne order by à partir de l'expression renvoyée par la méthode OrderByField() . De toute évidence, ce que j'ai essayé ne fonctionne pas. Le member devient toujours nul.


Edit: J'ai apporté quelques modifications aux paramètres de type des méthodes. Désolé de ne pas le faire la première fois.

Réponse acceptée

Apparemment, l' Expression<Func<T, object>> n'est pas équivalente à l' Expression<Func<T, K>> , et ne peut donc pas être utilisée pour remplacer directement l' Queryable.OrderBy<T, K> requis ultérieurement par Queryable.OrderBy<T, K> et des méthodes similaires.

Il est toujours possible de le faire fonctionner à l'aide de la classe Expression en créant une Expression.Lambda LambdaExpression non générique via la méthode Expression.Lambda et en émettant de manière dynamique un appel à la méthode Queryable correspondante.

Voici tout ce qui est encapsulé dans une méthode d'extension personnalisée (une version modifiée de ma réponse à Comment utiliser une chaîne pour créer un EF par ordre? ):

public static partial class QueryableExtensions
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "OrderBy");
    }
    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "OrderByDescending");
    }
    public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "ThenBy");
    }
    public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "ThenByDescending");
    }
    private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector, string method)
    {
        var parameter = keySelector.Parameters[0];
        var body = keySelector.Body;
        if (body.NodeType == ExpressionType.Convert)
            body = ((UnaryExpression)body).Operand;
        var selector = Expression.Lambda(body, parameter);
        var methodCall = Expression.Call(
            typeof(Queryable), method, new[] { parameter.Type, body.Type },
            source.Expression, Expression.Quote(selector));
        return (IOrderedQueryable<T>)source.Provider.CreateQuery(methodCall);
    }
}

Un détail important ici est que Expression<Func<T, object>> introduit Expression.Convert pour les expressions renvoyant le type valeur, il doit donc être extrait du corps lambda réel, ce qui est accompli avec la partie suivante du code:

public static partial class QueryableExtensions
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "OrderBy");
    }
    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "OrderByDescending");
    }
    public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "ThenBy");
    }
    public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        return source.OrderBy(keySelector, "ThenByDescending");
    }
    private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector, string method)
    {
        var parameter = keySelector.Parameters[0];
        var body = keySelector.Body;
        if (body.NodeType == ExpressionType.Convert)
            body = ((UnaryExpression)body).Operand;
        var selector = Expression.Lambda(body, parameter);
        var methodCall = Expression.Call(
            typeof(Queryable), method, new[] { parameter.Type, body.Type },
            source.Expression, Expression.Quote(selector));
        return (IOrderedQueryable<T>)source.Provider.CreateQuery(methodCall);
    }
}

Réponse populaire

Vous avez besoin de deux types génériques pour votre classe Notes . La première consiste à pouvoir en dériver tout en permettant à l'expression de filtrer sur la classe dérivée. La seconde consiste à spécifier le type de propriété que vous souhaitez utiliser pour commander. Par exemple:

public abstract class Notes<T, TProperty> where T : Notes<T, TProperty>
{
    public abstract Expression<Func<T, TProperty>> OrderByField();

    public string GetOrderByFieldName()
    {
        //snip
    }
}

public class Message : Notes<Message, int>
{
    public int item_no { get; set; }

    public override Expression<Func<Message, int>> OrderByField()
    {
        return m => m.item_no;
    }
}

Cela devrait également permettre à la méthode GetOrderByFieldName de fonctionner.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi