Returned expression from the method to be used in OrderBy

I want to write an abstract method that needs to be overridden by child classes, and what that method does is return an expression that will later be used in LINQ OrderBy()

. Something like that:

Note: Message

inherits from class Notes

and MyModel

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

      

Now if I try to order this way:

var orderedQuery = myQueryable.OrderBy(OrderByField());

      

I am getting this error:

'Cannot pass type' System.Int32 'for input' System.Object '. LINQ to Entities only supports EDM listing of primitive or enumerated types.

I can tell directly that the type parameter object is causing the problem. So when I change the type parameter to int, it works fine as long as I order an int field (like a field item_no

).

Q1. How can I get this to work? I can of course use the string property OrderByField

instead of this expression method and the order of its expression, perhaps by writing some extension method for the IQueryable (perhaps using this excellent answer ). But I want to have more intellisense when setting up the order.

Q2. How can I get the name of the column order from the expression returned by the method OrderByField()

. Obviously what I've tried doesn't work. member

always gets null.


Edit: I made some changes to the type parameters of the methods. Sorry I didn't do this the first time.

+3


source to share


3 answers


Apparently Expression<Func<T, object>>

not equivalent Expression<Func<T, K>>

, so cannot be used as a direct replacement for more recent, required, Queryable.OrderBy<T, K>

and similar methods.

However, you can make it work with a class Expression

by making it non-generic LambdaExpression

through a Expression.Lambda

method and dynamically emitting a call to the appropriate method Queryable

.

Here's everything encapsulated in custom extension methods (modified version of my answer to How do I use a string to create an EF order by expression? ):



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

      

One important detail here is what it Expression<Func<T, object>>

enters Expression.Convert

for the return type expression, so it needs to be removed from the actual lambda body, which is executed with the following piece of code:

var body = keySelector.Body;
if (body.NodeType == ExpressionType.Convert)
    body = ((UnaryExpression)body).Operand;

      

+2


source


You will need several generic types for your class Notes

. First, you can extract from it and still resolve the filter expression on the derived class. The second is to indicate the type of property you want to use for the order. For example:

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

      



This should also allow the method GetOrderByFieldName

to work.

+2


source


Here's a solution that (almost) doesn't use reflection or expression effects: It takes advantage of the fact that LINQ ordering functions only have one common type in their result.

1, create an interface (only one generic parameter) and an implementation (with two generic parameters):

public interface ISortCrit<TSource>
{
    string SortFieldName { get; }

    IOrderedEnumerable<TSource> MakeOrderBy(IEnumerable<TSource> source);
    IOrderedEnumerable<TSource> MakeOrderByDescending(IEnumerable<TSource> source);
    IOrderedEnumerable<TSource> MakeThenBy(IOrderedEnumerable<TSource> source);
    IOrderedEnumerable<TSource> MakeThenByDescending(IOrderedEnumerable<TSource> source);

    IOrderedQueryable<TSource> MakeOrderBy(IQueryable<TSource> source);
    IOrderedQueryable<TSource> MakeOrderByDescending(IQueryable<TSource> source);
    IOrderedQueryable<TSource> MakeThenBy(IOrderedQueryable<TSource> source);
    IOrderedQueryable<TSource> MakeThenByDescending(IOrderedQueryable<TSource> source);
}

public class SortCrit<TSource, TSort> : ISortCrit<TSource>
{
    private readonly Expression<Func<TSource, TSort>> _sortExpression;
    private readonly Lazy<Func<TSource, TSort>> _sortDelegate;
    private readonly Lazy<string> _sortFieldName;

    public SortCrit(Expression<Func<TSource, TSort>> sortExpression)
    {
        _sortExpression = sortExpression;
        _sortDelegate = new Lazy<Func<TSource, TSort>>(() => sortExpression.Compile());
        _sortFieldName = new Lazy<string>(() => ((MemberExpression)sortExpression.Body).Member.Name);
    }

    public string SortFieldName => _sortFieldName.Value;

    public IOrderedEnumerable<TSource> MakeOrderBy(IEnumerable<TSource> source) => source.OrderBy(_sortDelegate.Value);
    public IOrderedEnumerable<TSource> MakeOrderByDescending(IEnumerable<TSource> source) => source.OrderByDescending(_sortDelegate.Value);
    public IOrderedEnumerable<TSource> MakeThenBy(IOrderedEnumerable<TSource> source) => source.ThenBy(_sortDelegate.Value);
    public IOrderedEnumerable<TSource> MakeThenByDescending(IOrderedEnumerable<TSource> source) => source.ThenBy(_sortDelegate.Value);

    public IOrderedQueryable<TSource> MakeOrderBy(IQueryable<TSource> source) => source.OrderBy(_sortExpression);
    public IOrderedQueryable<TSource> MakeOrderByDescending(IQueryable<TSource> source) => source.OrderByDescending(_sortExpression);
    public IOrderedQueryable<TSource> MakeThenBy(IOrderedQueryable<TSource> source) => source.ThenBy(_sortExpression);
    public IOrderedQueryable<TSource> MakeThenByDescending(IOrderedQueryable<TSource> source) => source.ThenByDescending(_sortExpression);
}

      

Second extensions for convenience:

public static class SortCrit
{
    public static ISortCrit<TSource> Create<TSource, TSort>(Expression<Func<TSource, TSort>> sortExpression) => new SortCrit<TSource, TSort>(sortExpression);

    public static IOrderedEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderBy(source);
    public static IOrderedEnumerable<TSource> OrderByDescending<TSource>(this IEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderByDescending(source);
    public static IOrderedEnumerable<TSource> ThenBy<TSource>(this IOrderedEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenBy(source);
    public static IOrderedEnumerable<TSource> ThenByDescending<TSource>(this IOrderedEnumerable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenByDescending(source);

    public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderBy(source);
    public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeOrderByDescending(source);
    public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenBy(source);
    public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> source, ISortCrit<TSource> crit) => crit.MakeThenByDescending(source);
}

      

Using:

var messageCrit = SortCrit.Create((Message m) => m.ItemNo);

IEnumerable<Message> msgs = ...;
msgs.OrderBy(messageCrit);

      

0


source







All Articles