Reputation: 4492
I have the below method. It returns expression which is called by my repository Get method
public Func<IQueryable<Level>, IOrderedQueryable<Level>> GetOrderByExpression()
{
if (request == null)
{
request = new OrderByRequest
{
IsAscending = true, PropertyName = "Name" // CreatedDate , LevelNo etc
};
}
if (string.IsNullOrWhiteSpace(request.PropertyName))
{
request.PropertyName = "Name";
}
Type entityType = typeof(Level);
ParameterExpression parameterExpression = Expression.Parameter(entityType, "x");
PropertyInfo propertyInfo = entityType.GetProperty(request.PropertyName);
Expression<Func<Level, object>> sortExpression =
Expression.Lambda<Func<Level, object>>(
Expression.Convert(Expression.Property(parameterExpression, request.PropertyName),
Type.GetType(propertyInfo.PropertyType.FullName)), parameterExpression);
Func<IQueryable<Level>, IOrderedQueryable<Level>> expression = request.IsAscending
? (Func<IQueryable<Level>, IOrderedQueryable<Level>>)(x => x.OrderBy(sortExpression))
: (x => x.OrderByDescending(sortExpression));
return expression;
}
Repository is calling like below (removed unnecessary codes for clarity):
public virtual IQueryable<TEntity> Get(
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null)
{
var query = DbContext.Set<TEntity>().AsQueryable();
if (orderBy != null)
{
query = orderBy(query);
}
}
The above method is working perfectly for string type of properties of Level class. But for the other types (like Integer/DateTime etc) it is not working and throwing error
Expression of type 'System.Int32' cannot be used for return type 'System.Object'
I want to make this method a generic OrderByExpression provider, and it will take the property names at runtime (this name will come from client side), so that it can work with any property of that given object. Is it possible?
Upvotes: 1
Views: 1406
Reputation: 4492
I found an alternative way.
public Func<IQueryable<Level>, IOrderedQueryable<Level>> GetOrderByFunc()
{
if (request == null)
{
request = new OrderByRequest
{
IsAscending = true,
PropertyName = "Name" // CreatedDate , LevelNo etc
};
}
if (string.IsNullOrWhiteSpace(request.PropertyName))
{
request.PropertyName = "Name";
}
Tuple<Expression, Type> selector = GetSelector(new List<string>() {request.PropertyName});
Type type = selector.Item2;
Type[] argumentTypes = new[] { typeof(Level), type };
var orderByMethod = typeof(Queryable).GetMethods()
.First(method => method.Name == "OrderBy"
&& method.GetParameters().Count() == 2)
.MakeGenericMethod(argumentTypes);
var orderByDescMethod = typeof(Queryable).GetMethods()
.First(method => method.Name == "OrderByDescending"
&& method.GetParameters().Count() == 2)
.MakeGenericMethod(argumentTypes);
if (request.IsAscending)
return query => (IOrderedQueryable<Level>)
orderByMethod.Invoke(null, new object[] {query, selector.Item1});
else
return query => (IOrderedQueryable<Level>)
orderByDescMethod.Invoke(null, new object[] {query, selector.Item1});
}
private static Tuple<Expression, Type> GetSelector(IEnumerable<string> propertyNames)
{
var parameter = Expression.Parameter(typeof(Level));
Expression body = parameter;
foreach (var property in propertyNames)
{
body = Expression.Property(body,
body.Type.GetProperty(property));
}
return Tuple.Create(Expression.Lambda(body, parameter) as Expression
, body.Type);
}
Upvotes: 0
Reputation: 205729
The declaration of OrderBy
is as follows
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector
)
As you see, there is a second generic argument TKey
which when the selector represents a property is the type of the property. The problem with your code is that you assume that every property can be represented by Func<TSource, object>
which in general is incorrect.
Here is a generic function that does correctly what you are trying to accomplish
public static class QueryableUtils
{
public static Func<IQueryable<TSource>, IOrderedQueryable<TSource>> OrderByFunc<TSource>(string propertyName, bool ascending = true)
{
var source = Expression.Parameter(typeof(IQueryable<TSource>), "source");
var item = Expression.Parameter(typeof(TSource), "item");
var member = Expression.Property(item, propertyName);
var selector = Expression.Quote(Expression.Lambda(member, item));
var body = Expression.Call(
typeof(Queryable), ascending ? "OrderBy" : "OrderByDescending",
new Type[] { item.Type, member.Type },
source, selector);
var expr = Expression.Lambda<Func<IQueryable<TSource>, IOrderedQueryable<TSource>>>(body, source);
var func = expr.Compile();
return func;
}
}
Using it in your case would be something like this
public Func<IQueryable<Level>, IOrderedQueryable<Level>> GetOrderByExpression()
{
if (request == null)
{
request = new OrderByRequest
{
IsAscending = true, PropertyName = "Name" // CreatedDate , LevelNo etc
};
}
if (string.IsNullOrWhiteSpace(request.PropertyName))
{
request.PropertyName = "Name";
}
return QueryableUtils.OrderByFunc<Level>(request.PropertyName, request.IsAscending);
}
Upvotes: 1