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;