I wanna create a sorting generic Func Creating Func<IQueryable<T>, IOrderedQueryable<T>> in an extension method :
public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(this KeyValuePair<string, SortingType> keyValuePair)
{
//I expect the following result
//Func<IQueryable<T>, IOrderedQueryable<T>> orderby = q => q.OrderByDescending(c => c.Name);
//keyValuePair.Key is name of property in type of T that I should sort T by it
switch (keyValuePair.Value)
{
case SortingType.Ascending:
// Creating Ascending Sorting Func
break;
case SortingType.Descending:
// Creating Descending Sorting Func
break;
default :
break;
}
}
Could you guide me, how I can do it?
Edit:
this sorting also, contains of count of a T's navigation property.
e.g:
// keyValuePair.Key equals "User.Count"
// User is a navigation property of T
Func<IQueryable<T>, IOrderedQueryable<T>> orderby = q => q.OrderByDescending(c => c.User.Count);
Edit:
I changed GetSelector as the following, but an exception has occurred in bodyExpression.
public static Expression GetSelector<T>(string propertyName)
{
ParameterExpression parameter = Expression.Parameter(typeof(T));
if (propertyName.Contains("."))
{
propertyName = propertyName.Substring(0, propertyName.IndexOf("."));
Type navigationPropertyCollectionType = typeof(T).GetProperty(propertyName).PropertyType;
if (navigationPropertyCollectionType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
Expression countParameter = Expression.Parameter(navigationPropertyCollectionType, "c");
MemberExpression countExpression = Expression.Property(countParameter, "Count");
//Exception: Instance property 'Users(ICollection`1)' is not defined for type 'System.Int32'
var bodyExpression = Expression.Property(countExpression, propertyName, countParameter);
return Expression.Lambda(bodyExpression, parameter);
}
}
MemberExpression bodyMemberExpression = Expression.Property(parameter, typeof(T).GetProperty(propertyName));
return Expression.Lambda(bodyMemberExpression, parameter);
}
So the first thing that we'll need is a method that can get the selector expression that selects out that property value when given the property name. It will need to build the expression from scratch:
public static Tuple<Expression, Type> GetSelector<T>(IEnumerable<string> propertyNames)
{
var parameter = Expression.Parameter(typeof(T));
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);
}
Note that since this results in a chain of properties the method also returns the type of the final property, as that wouldn't be a particularly easy bit of information to access from the caller's perspective.
Because we don't know the return type of the property when calling Selector so we have no choice but to leave the return type of this method typed to Expression. We can't cast it to an Expression<Func<T, Something>>. We could have it return a Expression<Func<T, object>>, and that would work for all properties that select out a reference type, but this wouldn't be able to box value types, so it would throw a runtime exception in those cases.
Now, because we don't know the exact type of that expression we can't call OrderBy or OrderByDescending directly. We need to grab those methods through reflection and use MakeGenericMethod so that they can be created using the proper type based on inspection of that property using reflection.
public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(
this Tuple<IEnumerable<string>, SortingType> sortCriteria)
{
var selector = GetSelector<T>(sortCriteria.Item1);
Type[] argumentTypes = new[] { typeof(T), selector.Item2 };
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 (sortCriteria.Item2 == SortingType.Descending)
return query => (IOrderedQueryable<T>)
orderByDescMethod.Invoke(null, new object[] { query, selector.Item1 });
else
return query => (IOrderedQueryable<T>)
orderByMethod.Invoke(null, new object[] { query, selector.Item1 });
}
You would need to use Expressions
public static Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByFunc<T>(this KeyValuePair<string, SortingType> keyValuePair)
{
Func<IQueryable<T>, IOrderedQueryable<T>> result;
var p1 = Expression.Parameter(typeof (T), "p1");
var prop = Expression.PropertyOrField(p1, keyValuePair.Key);
var lambada = Expression.Lambda<Func<T, object>>(prop, new ParameterExpression[] {p1});
//keyValuePair.Key is name of property in type of T that I should sort T by it
switch (keyValuePair.Value)
{
case SortingType.Ascending:
result = source => source.OrderBy(lambada);
break;
case SortingType.Descending:
result = source => source.OrderByDescending(lambada);
break;
default:
throw new NotImplementedException();
break;
}
return result;
}
Related
I have an initial workflow put together that allows me to perform inclusive searches for string properties of objects contained in an IQueryable:
public static IQueryable ApplySearch(this IQueryable queryable, string search)
{
// validation omitted for brevity
var expression = queryable
.Cast<object>()
.Where(item => item.SearchStringTree(search))
.Expression;
var result = queryable.Provider.CreateQuery(expression);
return result;
}
static bool SearchStringTree<T>(this T value, string search) =>
value.GetObjectStrings().Any(s => s.Contains(search.ToLower()));
static IEnumerable<string> GetObjectStrings<T>(this T value)
{
var strings = new List<string>();
var properties = value.GetType()
.GetProperties()
.Where(x => x.CanRead);
foreach (var prop in properties)
{
var t = prop.PropertyType.ToString().ToLower();
var root = t.Split('.')[0];
if (t == "system.string")
{
strings.Add(((string)prop.GetValue(value)).ToLower());
}
else if (!(root == "system"))
{
strings.AddRange(prop.GetValue(value).GetObjectStrings());
}
}
return strings;
}
Would it be possible to apply this concept in a way that Entity Framework can translate prior to DbContext execution?
I've been looking into potentially using Expression Trees to accomplish this.
Here's a working Repl.it showing the IQueryable implementation above.
You definitely need to build expression tree, basically multi or (C# ||) predicate expression for all (nested) string properties.
Something like this (expression version of your code):
public static class FilterExpression
{
public static IQueryable<T> ApplySearch<T>(this IQueryable<T> source, string search)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (string.IsNullOrWhiteSpace(search)) return source;
var parameter = Expression.Parameter(typeof(T), "e");
// The following simulates closure to let EF Core create parameter rather than constant value (in case you use `Expresssion.Constant(search)`)
var value = Expression.Property(Expression.Constant(new { search }), nameof(search));
var body = SearchStrings(parameter, value);
if (body == null) return source;
var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(predicate);
}
static Expression SearchStrings(Expression target, Expression search)
{
Expression result = null;
var properties = target.Type
.GetProperties()
.Where(x => x.CanRead);
foreach (var prop in properties)
{
Expression condition = null;
var propValue = Expression.MakeMemberAccess(target, prop);
if (prop.PropertyType == typeof(string))
{
var comparand = Expression.Call(propValue, nameof(string.ToLower), Type.EmptyTypes);
condition = Expression.Call(comparand, nameof(string.Contains), Type.EmptyTypes, search);
}
else if (!prop.PropertyType.Namespace.StartsWith("System."))
{
condition = SearchStrings(propValue, search);
}
if (condition != null)
result = result == null ? condition : Expression.OrElse(result, condition);
}
return result;
}
}
The non generic version is not much different - just instead of Where extension method you need to generate a "call" to it in the query expression tree:
public static IQueryable ApplySearch(this IQueryable source, string search)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (string.IsNullOrWhiteSpace(search)) return source;
var parameter = Expression.Parameter(source.ElementType, "e");
var value = Expression.Property(Expression.Constant(new { search }), nameof(search));
var body = SearchStrings(parameter, value);
if (body == null) return source;
var predicate = Expression.Lambda(body, parameter);
var filtered = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { source.ElementType },
source.Expression, Expression.Quote(predicate));
return source.Provider.CreateQuery(filtered);
}
While this works, it's not much useful because all LINQ extensions methods (including AsEnumerable(),ToList()` etc.) work with generic interface.
Also in both cases, the type of the query element must be known in advance, e.g. T in the generic version, query.ElementType in the non generic version. This is because expression tree are processed in advance, when there are no "objects", hence it can't use item.GetType(). For the same reason, IQueryable translators like EF Core don't like Cast "calls" inside the query expression tree.
I've got an function which generates an expression to filter a table by it's primary key, when passed in an Object[], this is very similar to Find function except that it doesn't materialize so you can pass an IQueryable around afterwards
public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.{propertyName} == new {id = id[i]}.id
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(
Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
p.ClrType)))
.Aggregate(Expression.AndAlso);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
This works by first getting the primary keys for a table, it creates binary expression foreach property, the Id is wrapped in an anonymous type to leverage the query cache. This is working fine. However, I'd like to take this a step further.
I'd like to preserve the Expression so I don't have to generate it each time I pass on a new set of ids, How can I store this Expression while still leveraging the Query Cache?
Edit TL;DR
So I'm attempt to cache it using array access in a static class as suggest, however I'm encountering an error:
public class PrimaryKeyFilterContainer<T>
{
const string ANON_ID_PROP = "id";
static Expression<Func<T, bool>> _filter;
Type ANON_TYPE = new { id = (object)0 }.GetType();
public object[] id { get; set; }
public PrimaryKeyFilterContainer()
{
}
public Expression<Func<T, bool>> GetFilter(DbContext dbContext, object[] id)
{
this.id = id;
if(null == _filter)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
var parameter = Expression.Parameter(typeof(T), "e");
var body = keyProperties
// e => e.PK[i] == id[i]
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Convert(BuildNewExpression(i),
p.ClrType)))
.Aggregate(Expression.AndAlso);
_filter = Expression.Lambda<Func<T, bool>>(body, parameter);
}
return _filter;
}
NewExpression BuildNewExpression(int index)
{
var currentObject = Expression.Constant(this);
var fieldAccess = Expression.PropertyOrField(currentObject, nameof(id));
var arrayAccess = Expression.ArrayAccess(fieldAccess, Expression.Constant(index));
return Expression.New(ANON_TYPE.GetConstructor(new[] { typeof(object) }), arrayAccess);
}
}
No coercion operator is defined between types '<>f__AnonymousType0`1[System.Object]' and 'System.Int32'
I'm getting closer but I'm not sure if it's going to work still.
As I mentioned in the comments, the main problem is that we cannot use array index access inside the expression tree - EF6 throws not supported exception and EF Core turns it into client evaluation.
So we need to store the keys in a class with dynamic count of properties and property types. Fortunately the System.Tuple generic classes provide such functionality, and can be used in both EF6 and EF Core.
Following is a class that implements the above idea:
public class PrimaryKeyFilter<TEntity>
where TEntity : class
{
object valueBuffer;
Func<object[], object> valueArrayConverter;
public PrimaryKeyFilter(DbContext dbContext)
{
var keyProperties = dbContext.GetPrimaryKeyProperties<TEntity>();
// Create value buffer type (Tuple) from key properties
var valueBufferType = TupleTypes[keyProperties.Count - 1]
.MakeGenericType(keyProperties.Select(p => p.ClrType).ToArray());
// Build the delegate for converting value array to value buffer
{
// object[] values => new Tuple(values[0], values[1], ...)
var parameter = Expression.Parameter(typeof(object[]), "values");
var body = Expression.New(
valueBufferType.GetConstructors().Single(),
keyProperties.Select((p, i) => Expression.Convert(
Expression.ArrayIndex(parameter, Expression.Constant(i)),
p.ClrType)));
valueArrayConverter = Expression.Lambda<Func<object[], object>>(body, parameter).Compile();
}
// Build the predicate expression
{
var parameter = Expression.Parameter(typeof(TEntity), "e");
var valueBuffer = Expression.Convert(
Expression.Field(Expression.Constant(this), nameof(this.valueBuffer)),
valueBufferType);
var body = keyProperties
// e => e.{propertyName} == valueBuffer.Item{i + 1}
.Select((p, i) => Expression.Equal(
Expression.Property(parameter, p.Name),
Expression.Property(valueBuffer, $"Item{i + 1}")))
.Aggregate(Expression.AndAlso);
Predicate = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
}
}
public Expression<Func<TEntity, bool>> Predicate { get; }
public void SetValues(params object[] values) =>
valueBuffer = valueArrayConverter(values);
static readonly Type[] TupleTypes =
{
typeof(Tuple<>),
typeof(Tuple<,>),
typeof(Tuple<,,>),
typeof(Tuple<,,,>),
typeof(Tuple<,,,,>),
typeof(Tuple<,,,,,>),
typeof(Tuple<,,,,,,>),
typeof(Tuple<,,,,,,,>),
};
}
You can create and store an instance of the class. Then use the expression returned by the Predicate property inside the query. And SetValues method to set the parameters.
The drawback is that the value storage is bound to the class instance, hence it cannot be used concurrently. The original approach works well in all scenarios, and the performance impact IMO should be negligible, so you might consider staying on it.
I have a dictionary declared like this:
private Dictionary<string, Expression<Func<Part, object>>> _orders = new Dictionary<string, Expression<Func<Part, object>>>()
{
{"Name", x => x.Name}, //string
{"Code", x => x.Code}, //string
{"EnterPrice", x => x.EnterPrice}, //decimal
{"ExitPrice", x => x.ExitPrice}, //decimal
{"IsActive", x => (bool)x.Active }, //bool
{"Quantity", x => x.Quantity}, //decimal
{"Reserved", x => x.Reserved}, //decimal
};
I try to bring data using the following code:
NameValueCollection filter = HttpUtility.ParseQueryString(Request.RequestUri.Query);
string sortField = filter["sortField"];
string sortOrder = filter["sortOrder"];
Func<IQueryable<Part>, IOrderedQueryable<Part>> orderBy = x => x.OrderBy(p => p.Id);
if (!string.IsNullOrEmpty(sortField) && _orders.ContainsKey(sortField))
{
bool sortMode = !string.IsNullOrEmpty(sortOrder) && sortOrder != "desc";
if (sortMode)
{
orderBy = x => x.OrderBy(_orders[sortField]);
}
else
{
orderBy = x => x.OrderByDescending(_orders[sortField]);
}
}
return Ok(this.DbService.Query(null, filterQuery));
And Query method is:
public IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, bool noTracking = true)
{
IQueryable<TEntity> query = DbContext.Set<TEntity>();
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null) query = orderBy(query);
return noTracking ? query.AsNoTracking() : query;
}
But when the sort column is not string I obtain the following exception
"Unable to cast the type 'System.Boolean' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.","ExceptionType":"System.NotSupportedException","StackTrace":" at System.Web.Http.ApiController.<InvokeActionWithExceptionFilters>d__1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()"}
I think that the dictionary declaration and/or initialization is wrong because if I do not have any sort set by browser then the default order will be x=>x.Id (which is declared inline) and it doesn't crash even if Id is long. Can I declare the dictionary in a different way to solve my problem?
PROBLEM SOLVED
I removed the dictionary and I added the following extension which receive the field name and sort mode as parametters
public static class LinqExtension
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool ascending = true)
{
var type = typeof(T);
var parameter = Expression.Parameter(type, "p");
PropertyInfo property;
Expression propertyAccess;
if (ordering.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = ordering.Split('.');
property = type.GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
property = property.PropertyType.GetProperty(childProperties[i]);
propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = typeof(T).GetProperty(ordering);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable),
ascending ? "OrderBy" : "OrderByDescending",
new[] { type, property.PropertyType }, source.Expression,
Expression.Quote(orderByExp));
//return source.OrderBy(x => orderByExp);
return source.Provider.CreateQuery<T>(resultExp);
}
}
Also solution provided by Ivan Stoev works
The dictionary definition is ok - there is no good way to declare it to have values with different type.
The problem is that Expression<Func<T, object>> definition generates additional Expression.Convert for value type properties. To make it work with EF, the convert expression must be removed and corresponding Queryable method must be called dynamically. It can be encapsulated in a custom extension method like this:
public static class QueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector, bool ascending)
{
var selectorBody = keySelector.Body;
// Strip the Convert expression
if (selectorBody.NodeType == ExpressionType.Convert)
selectorBody = ((UnaryExpression)selectorBody).Operand;
// Create dynamic lambda expression
var selector = Expression.Lambda(selectorBody, keySelector.Parameters);
// Generate the corresponding Queryable method call
var queryBody = Expression.Call(typeof(Queryable),
ascending ? "OrderBy" : "OrderByDescending",
new Type[] { typeof(T), selectorBody.Type },
source.Expression, Expression.Quote(selector));
return source.Provider.CreateQuery<T>(queryBody);
}
}
and the usage in your scenario could be like this:
if (!string.IsNullOrEmpty(sortField) && _orders.ContainsKey(sortField))
orderBy = x => x.OrderBy(_orders[sortField], sortOrder != "desc");
My method receives all DataTables parameters to sort table by column clicked. I call this method from controller of each page list.
I'm looking for a better way to do this like a generic method for all types: string, int, decimal, double, bool (nullable or not). But I can't find it.
My current code:
public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
var iColumn = model.Order.FirstOrDefault().Column;
var property = typeof(T).GetProperty(model.Columns.ToArray()[iColumn].Data);
var param = Expression.Parameter(typeof(T));
var final = Expression.Property(param, property);
var isDirAsc = model.Order.FirstOrDefault().Dir.Equals("asc");
if (property.PropertyType == typeof(string))
{
var lambda = Expression.Lambda<Func<T, string>>(final, param).Compile();
return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}
else if (property.PropertyType == typeof(int))
{
var lambda = Expression.Lambda<Func<T, int>>(final, param).Compile();
return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}
else if (property.PropertyType == typeof(bool))
{
var lambda = Expression.Lambda<Func<T, bool>>(final, param).Compile();
return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}
else if (property.PropertyType == typeof(decimal))
{
var lambda = Expression.Lambda<Func<T, decimal>>(final, param).Compile();
return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}
else if (property.PropertyType == typeof(double))
{
var lambda = Expression.Lambda<Func<T, double>>(final, param).Compile();
return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}
return list;
}
I want to do something like this: (But this code doesn't work)
public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
var iColumn = model.Order.FirstOrDefault().Column;
var property = typeof(T).GetProperty(model.Columns.ToArray()[iColumn].Data);
var param = Expression.Parameter(typeof(T));
var final = Expression.Property(param, property);
var isDirAsc = model.Order.FirstOrDefault().Dir.Equals("asc");
var lambda = Expression.Lambda<Func<T, dynamic>>(final, param).Compile();
return isDirAsc ? list.OrderBy(lambda).ToList() : list.OrderByDescending(lambda).ToList();
}
You can just call the Enumerable.OrderBy method using reflection. That way, you don’t have to know the type at compile-time. To do that, you just need to get the method, and create a generic method using the property’s type:
private IEnumerable<T> Sort<T> (List<T> list, string propertyName)
{
MethodInfo orderByMethod = typeof(Enumerable).GetMethods().First(mi => mi.Name == "OrderBy" && mi.GetParameters().Length == 2);
PropertyInfo pi = typeof(T).GetProperty(propertyName);
MethodInfo orderBy = orderByMethod.MakeGenericMethod(typeof(T), pi.PropertyType);
ParameterExpression param = Expression.Parameter(typeof(T));
Delegate accessor = Expression.Lambda(Expression.Property(param, pi), param).Compile();
return (IEnumerable<T>)orderBy.Invoke(null, new object[] { lst, accessor });
}
Note that I abstracted out the stuff about your model to keep this method generic enough. It can basically sort by any property on a list by just specifying the property name. Your original method would then look like this:
public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
var iColumn = model.Order.FirstOrDefault().Column;
string propertyName = model.Columns.ToArray()[iColumn].Data;
return Sort(list, propertyName).ToList();
}
It's works fine for me: (Thanks #Poke)
https://stackoverflow.com/a/31393168/5112444
My final method:
private IEnumerable<T> Sort<T>(IEnumerable<T> list, string propertyName, bool isAsc)
{
MethodInfo orderByMethod = typeof(Enumerable).GetMethods().First(mi => mi.Name == (isAsc ? "OrderBy" : "OrderByDescending") && mi.GetParameters().Length == 2);
PropertyInfo pi = typeof(T).GetProperty(propertyName);
MethodInfo orderBy = orderByMethod.MakeGenericMethod(typeof(T), pi.PropertyType);
ParameterExpression param = Expression.Parameter(typeof(T));
Delegate accessor = Expression.Lambda(Expression.Call(param, pi.GetGetMethod()), param).Compile();
return (IEnumerable<T>)orderBy.Invoke(null, new object[] { list, accessor });
}
Your suggested method almost works. You need to change two things in order to make it work:
public List<T> OrderingList<T>(List<T> list, DataTablesParam model)
{
var iColumn = model.Order.FirstOrDefault().Column;
var property = typeof(T).GetProperty(model.Columns.ToArray()[iColumn].Data);
var param = Expression.Parameter(typeof(T), "p");
Expression final = Expression.Property(param, property);
// Boxing of value types
if (property.PropertyType.IsValueType) {
final = Expression.MakeUnary(ExpressionType.Convert, final, typeof(object));
}
var isDirAsc = model.Order.FirstOrDefault().Dir.Equals("asc");
// VVVVVV
var lambda = Expression.Lambda<Func<T, object>>(final, param).Compile();
return isDirAsc
? list.OrderBy(lambda).ToList()
: list.OrderByDescending(lambda).ToList();
}
Instead of using dynamic use object, since every type is an object.
If you have a value type, you need a boxing operation, i.e. you must cast the value to object (object)i. This is done with a unary convert operation:
Expression final = Expression.Property(param, property);
if (property.PropertyType.IsValueType) {
final = Expression.MakeUnary(ExpressionType.Convert, final, typeof(object));
}
Note also that final is declared explicitly as Expression, since the expression type might change from property to unary expression.
Beyond just not being very generic, your solution also requires a lot of extra memory because you're copying the list with LINQ. You can avoid this using List.Sort.
I would do:
static void SortBy<T>(List<T> list, MemberInfo member, bool desc)
{
Comparison<T> cmp = BuildComparer<T>(member, desc);
list.Sort(cmp);
}
static Comparison<T> BuildComparer<T>(MemberInfo member, bool desc)
{
var left = Expression.Parameter(typeof(T));
var right = Expression.Parameter(typeof(T));
Expression cmp = Expression.Call(
Expression.MakeMemberAccess(desc ? right : left, member),
"CompareTo",
Type.EmptyTypes,
Expression.MakeMemberAccess(desc ? left : right, member));
return Expression.Lambda<Comparison<T>>(cmp, left, right).Compile();
}
I found a better way to do this. I had to do 3 steps:
1 - Add the package "Linq Dynamic" in project:
Install-Package System.Linq.Dynamic.Library
2 - Import the package in Class:
using System.Linq.Dynamic;
3 - Order list by the string name of property:
list.OrderBy(stringPropertyName); //asc
list.OrderBy(stringPropertyName + " descending"); //des
It work perfectly for me.
I'm getting this exception when I run this code.
ParameterExpression of type System.Int64 cannot be used for delegate parameter of type System.Object
I know it's something to do with the Expression.Lambda<func<object,bool>> part of the code. Overall, I want to pass any type of ParameterExpression into this method and it will call the expression.
public static IQueryable<T> OrderData<T>(IQueryable<T> data)
{
try
{
Order order = Order.ASC;
var result = Enum.TryParse<Order>(_gridSettings.SortOrder, true, out order);
if (_gridSettings.IsSearch)
{
data = ExpressionSort(order, data, typeof(T).GetProperty(_gridSettings.SortColumn));
}
else
{
data = ExpressionSort(order, data, _defaultColumn);
}
}
catch (Exception ex)
{
log.WriteLog(MethodBase.GetCurrentMethod(), LogLevel.FATAL, ex);
}
return data;
}
private static IQueryable<T> ExpressionSort<T>(Order order, IQueryable<T> data, PropertyInfo property)
{
// Compose the expression tree that represents the parameter to the predicate.
ParameterExpression paramExpression = Expression.Parameter(property.PropertyType, property.Name);
IQueryable<T> queryableData = data.AsQueryable<T>();
switch (order)
{
case Order.ASC:
return ExecuteCall(paramExpression, paramExpression, queryableData, "OrderBy");
case Order.DESC:
return ExecuteCall(paramExpression, paramExpression, queryableData, "OrderByDescending");
}
return data;
}
private static IQueryable<T> ExecuteCall<T>(Expression expression, ParameterExpression paramExpression, IQueryable<T> queryableData, string linqMethod)
{
MethodCallExpression callExpression = Expression.Call(
typeof(Queryable),
linqMethod,
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<object, bool>>(expression, new ParameterExpression[] { paramExpression }));
// Create an executable query from the expression tree.
return queryableData.Provider.CreateQuery<T>(callExpression);
}
EDIT:
I did see this answer to a similar question
Expression of type 'System.Int32' cannot be used for return type 'System.Object'
I do not know how to apply it to my code though
EDIT 2:
The main issue is that thisExpression.Lambda<Func<object, bool>>(conversion, new ParameterExpression[] { paramExpression })); line is giving me an exception. paramExpression contains an Int64 but its expectinng an object. I dont know how to dynamically tell the Func from the information I already have or if that is possible.
GOAL:
I am trying to do something like this data.OrderBy(x=>x.DynamicProperty);
This is what you asked for, I think... I've tested it and it seems to work.
// Caching of the reflection
private static readonly MethodInfo orderByMethod = GetOrderByMethod("OrderBy");
private static readonly MethodInfo orderByDescendingMethod = GetOrderByMethod("OrderByDescending");
private static IOrderedQueryable<TSource> ExpressionSort<TSource>(Order order, IQueryable<TSource> source, PropertyInfo property)
{
// Compose the expression tree that represents the parameter to
// the predicate.
// The expression you would use is source => source.Property,
// The parameter of the lambda, source
ParameterExpression sourceExpression = Expression.Parameter(typeof(TSource), "source");
// Accessing the expression
MemberExpression propertyExpression = Expression.Property(sourceExpression, property);
// The full lambda expression. We don't need the
// Expression.Lambda<>, but still the keySelector will be an
// Expression<Func<,>>, because Expression.Lambda does it
// authomatically. LambdaExpression is simply a superclass of
// all the Expression<Delegate>
LambdaExpression keySelector = Expression.Lambda(propertyExpression, sourceExpression);
// The OrderBy method we will be using, that we have cached
// in some static fields
MethodInfo method = order == Order.ASC ? orderByMethod : orderByDescendingMethod;
// Adapted from Queryable.OrderBy (retrieved from the reference
// source code), simply changed the way the OrderBy method is
// retrieved to "method"
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(Expression.Call(null, method.MakeGenericMethod(new Type[]
{
typeof(TSource),
property.PropertyType
}), new Expression[]
{
source.Expression,
Expression.Quote(keySelector)
}));
}
private static MethodInfo GetOrderByMethod(string methodName)
{
// Here I'm taking the long and more correct way to find OrderBy/
// OrderByDescending: looking for a public static method with the
// right name, with two generic arguments and that has the
// parameters related to those two generic arguments in a certain
// way (they must be IQueryable<arg0> and Expression<Func<arg0,
// arg1>>
MethodInfo orderByMethod = (from x in typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == methodName
let generics = x.GetGenericArguments()
where generics.Length == 2
let parameters = x.GetParameters()
where parameters.Length == 2 &&
parameters[0].ParameterType == typeof(IQueryable<>).MakeGenericType(generics[0]) &&
parameters[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(generics))
select x).Single();
return orderByMethod;
}
Please don't ever use AsQueryable<>(). It doesn't do what you think, and it is totally useless outside unit testing and very specific use cases.
You could use my OrderByString extension. https://www.nuget.org/packages/OrderByString/ It takes strings for sort parameters. The sort parameters strings can be comma-delimited lists of property names, such as "Prop1,Prop2" or it can include a sort order as in "Prop1 DESC, Prop2 ASC".
using OrderByExtensions;
public static IQueryable<T> OrderData<T>(IQueryable<T> data)
{
try
{
Order order = Order.ASC;
var result = Enum.TryParse<Order>(_gridSettings.SortOrder, true, out order);
var sortColumn = _gridSettings.IsSearch ? _gridSettings.SortColumn : _defaultColumn;
data = data.OrderBy(sortColumn + " " + _gridSettings.SortOrder.ToString());
}
catch (Exception ex)
{
log.WriteLog(MethodBase.GetCurrentMethod(), LogLevel.FATAL, ex);
}
return data;
}
OR
You could use the following GetExpressionForProperty method that returns the expected sort expression for OrderBy, OrderByDescending, ThenBy, or ThenByDescending.
private static IQueryable<T> ExpressionSort<T>(Order order, IQueryable<T> data, PropertyInfo property)
{
Expression<Func<T, object>> propertyExpression = GetExpressionForProperty<T>(property);
return order == Order.DESC ? data.OrderByDescending(propertyExpression) : data.OrderBy(propertyExpression);
}
static Expression<Func<TSource, object>> GetExpressionForProperty<TSource>(PropertyInfo propertyInfo)
{
var param = Expression.Parameter(typeof(TSource));
return Expression.Lambda<Func<TSource, object>>(
Expression.Convert(
Expression.Property(param, propertyInfo),
typeof(object)
)
, param);
}
Try using Expression.Convert. Here's a similar question that may give you some more guidance:
Expression of type 'System.Int32' cannot be used for return type 'System.Object'