I want to write an abstract method to be overridden by child classes, and what the method does is return an expression to be subsequently used in LINQ OrderBy()
. Something like this:
Note: Message
inherits from Notes
class, 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 by this way:
var orderedQuery = myQueryable.OrderBy(OrderByField());
I get this error:
'Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.'
I can rightaway say that type parameter object is the cause of the problem. So, when I change the type parameter to int, it works fine as long as I am ordering by an int field (e.g. the field item_no
).
Q1. How can I get this to work? Surely, I can use a string property OrderByField
instead of that expression-returning method and order by it, probably by writing some extension method for IQueryable (maybe using this great answer). But I want to have more intellisense while setting the order by.
Q2. How can I get the name of the order by column from the expression returned by the method OrderByField()
. Obviously what I have tried with doesn't work. The member
always gets null.
Edit: I have made some changes to the type parameters of the methods. Sorry for not doing it the first time.
Apparently the Expression<Func<T, object>>
is not equivalent of Expression<Func<T, K>>
, hence cannot be used as direct replacement of the later required by Queryable.OrderBy<T, K>
and similar methods.
Still it's possible to make it work with the help of Expression
class by creating a non generic LambdaExpression
via Expression.Lambda
method and dynamically emitting a call to the corresponding Queryable
method.
Here is all that encapsulated in a custom extension methods (a modified version of my answer to How to use a string to create a 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 that Expression<Func<T, object>>
introduces Expression.Convert
for value type returning expressions, so it needs to be stripped from the actual lambda body, which is accomplished with the following part of the code:
var body = keySelector.Body;
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
You need a couple of generic types for your Notes
class. The first is so you can derive from it and still allow the expression to filter on the derived class. The second is to specify the type of the property you wish to use to order by. 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 GetOrderByFieldName
method to work.
Here's a solution that does (almost) not use reflection or expression stuff: It exploits the fact that the ordering LINQ functions have just one generic type in their result.
1st, 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);
}
2nd some conveniance extensions:
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);
}
Usage:
var messageCrit = SortCrit.Create((Message m) => m.ItemNo);
IEnumerable<Message> msgs = ...;
msgs.OrderBy(messageCrit);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With