How to make a dynamic order in Entity Framework - c#

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

Related

LINQ method, which Dynamically ordering data by column name I send

What I want to do?
I want to create method, which Dynamically ordering data by column name I send.
First I created a project for testing. I see the project works super.
Test Project:
PropertyInfo pinfo = typeof(MockData).GetProperty(orderColumn);
switch (orderDirection)
{
case "asc":
mockDataList = q.OrderBy(o => pinfo.GetValue(o, null)).Skip(start).Take(length).ToList();
break;
case "desc":
mockDataList = q.OrderByDescending(o => pinfo.GetValue(o, null)).Skip(start).Take(length).ToList();
break;
}
After, I applied things, which I learned to my essential project that way:
Essential Project:
public IQueryable<T> RefactoringQuerybyPagination<T>(DataTablesRequestModel dataTablesRequestModel, IQueryable<T> query)
{
PropertyInfo pinfo = typeof(T).GetProperty(dataTablesRequestModel.OrderColumn);
switch (dataTablesRequestModel.OrderDirection)
{
case "asc":
query = query.OrderBy(o => pinfo.GetValue(o, null) != null).ThenBy(o => pinfo.GetValue(o, null));
break;
case "desc":
query = query.OrderByDescending(o => pinfo.GetValue(o, null) != null).ThenBy(o => pinfo.GetValue(o, null));
break;
}
query = query.Skip(dataTablesRequestModel.Start);
query = query.Take(dataTablesRequestModel.Length);
var test = query.ToList();
return query;
}
But it doesn't work and it gives me an error ("could not be translated...")
Solution I tried
public static IQueryable<T> OrderBy<T>(this IQueryable<T> items, string propertyName)
{
var typeOfT = typeof(T);
var parameter = Expression.Parameter(typeOfT, "parameter");
var propertyType = typeOfT.GetProperty(propertyName).PropertyType;
var propertyAccess = Expression.PropertyOrField(parameter, propertyName);
var orderExpression = Expression.Lambda(propertyAccess, parameter);
var expression = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { typeOfT, propertyType }, items.Expression, Expression.Quote(orderExpression));
return items.Provider.CreateQuery<T>(expression);
}
It works but I can't manipulate expression. (like o = > o.Column.HasValue).
All day, I worked on this, I'm really tired. Can anyone help me?
Try to create a QueryableExtensions with the the following code:
//required using System.Linq;
//required using System.Linq.Expressions;
public static class QueryableExtensions
{
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string columnName, bool isAscending = true)
{
if (String.IsNullOrEmpty(columnName))
{
return source;
}
ParameterExpression parameter = Expression.Parameter(source.ElementType, "");
MemberExpression property = Expression.Property(parameter, columnName);
LambdaExpression lambda = Expression.Lambda(property, parameter);
string methodName = isAscending ? "OrderBy" : "OrderByDescending";
Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
Then, in the LINQ statement, we could use the above method to sort the records:
public IActionResult CategoryIndex()
{
//CategoryName Descending
var result1 = _context.Categories.OrderBy("CategoryName", false).Select(c=>c.CategoryName).ToArray();
//CategoryID Descending
var result2 = _context.Categories.OrderBy("CategoryID", false).Select(c => c.CategoryID).ToArray();
return View();
}
The screenshot as below:
You can use the Dynamic LINQ to achieve what you are trying to do in a lot easier and simplified way. You can find the NuGet package here - System.Linq.Dynamic.Core
From your code I'm assuming your scenario does not involve sorting/ordering on multiple columns. If so, you can create an extension method like below -
// you'll need to add this
// using System.Linq.Dynamic.Core;
public static class IQueryableExtension
{
public static IQueryable<T> ApplyOrder<T>(this IQueryable<T> source, string column, string direction)
{
if (string.IsNullOrWhiteSpace(column))
return source;
var pinfo = typeof(T).GetProperty(column);
if (pinfo == null)
return source;
var order = (direction == "desc") ? $"{column} desc" : column;
return source.OrderBy(order);
}
}
You can use it from your existing code like -
public IQueryable<T> RefactoringQuerybyPagination<T>(DataTablesRequestModel dataTablesRequestModel, IQueryable<T> query)
{
// here is the call
query = query.ApplyOrder(dataTablesRequestModel.OrderColumn, dataTablesRequestModel.OrderDirection);
query = query.Skip(dataTablesRequestModel.Start);
query = query.Take(dataTablesRequestModel.Length);
var test = query.ToList();
return query;
}

Store Static Filter By Key Expression

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.

Linq orderbys from collection [duplicate]

This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
How do I specify the argument passed to orderby using a value I take as a parameter?
Ex:
List<Student> existingStudends = new List<Student>{ new Student {...}, new Student {...}}
Currently implementation:
List<Student> orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList();
Instead of c.Address, how can I take that as a parameter?
Example
string param = "City";
List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();
You can use a little bit of reflection to construct the expression tree as follows (this is an extension method):
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty,
bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}
orderByProperty is the Property name you want to order by and if pass true as parameter for desc, will sort in descending order; otherwise, will sort in ascending order.
Now you should be able to do existingStudents.OrderBy("City",true); or existingStudents.OrderBy("City",false);
Here's a possiblity using reflection...
var param = "Address";
var propertyInfo = typeof(Student).GetProperty(param);
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));
To expand on the answer by #Icarus: if you want the return type of the extension method to be an IOrderedQueryable instead of an IQueryable, you can simply cast the result as follows:
public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
1) Install System.Linq.Dynamic
2) Add the following code
public static class OrderUtils
{
public static string ToStringForOrdering<T, TKey>(this Expression<Func<T, TKey>> expression, bool isDesc = false)
{
var str = expression.Body.ToString();
var param = expression.Parameters.First().Name;
str = str.Replace("Convert(", "(").Replace(param + ".", "");
return str + (isDesc ? " descending" : "");
}
}
3) Write your switch for selecting of Lambda function
public static class SortHelper
{
public static Expression<Func<UserApp, object>> UserApp(string orderProperty)
{
orderProperty = orderProperty?.ToLowerInvariant();
switch (orderProperty)
{
case "firstname":
return x => x.PersonalInfo.FirstName;
case "lastname":
return x => x.PersonalInfo.LastName;
case "fullname":
return x => x.PersonalInfo.FirstName + x.PersonalInfo.LastName;
case "email":
return x => x.Email;
}
}
}
4) Use your helpers
Dbset.OrderBy(SortHelper.UserApp("firstname").ToStringForOrdering())
5) You can use it with pagging (PagedList)
public virtual IPagedList<T> GetPage<TOrder>(Page page, Expression<Func<T, bool>> where, Expression<Func<T, TOrder>> order, bool isDesc = false,
params Expression<Func<T, object>>[] includes)
{
var orderedQueryable = Dbset.OrderBy(order.ToStringForOrdering(isDesc));
var query = orderedQueryable.Where(where).GetPage(page);
query = AppendIncludes(query, includes);
var results = query.ToList();
var total = Dbset.Count(where);
return new StaticPagedList<T>(results, page.PageNumber, page.PageSize, total);
}
Explanation
System.Linq.Dynamic allows us to set string value in OrderBy method. But inside this extension the string will be parsed to Lambda. So I thought it would work if we will parse Lambda to string and give it to OrderBy method. And it works!
Here's something I came up with for dealing with a conditional Descending. You could combine this with other methods of generating the keySelector func dynamically.
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source,
System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
System.ComponentModel.ListSortDirection sortOrder
)
{
if (sortOrder == System.ComponentModel.ListSortDirection.Ascending)
return source.OrderBy(keySelector);
else
return source.OrderByDescending(keySelector);
}
Usage:
//imagine this is some parameter
var direction = System.ComponentModel.ListSortDirection.Ascending;
query = query.OrderBy(ec => ec.MyColumnName, direction);
Notice this allows you to chain this .OrderBy extension with a new parameter onto any IQueryable.
// perhaps passed in as a request of user to change sort order
// var direction = System.ComponentModel.ListSortDirection.Ascending;
query = context.Orders
.Where(o => o.Status == OrderStatus.Paid)
.OrderBy(ec => ec.OrderPaidUtc, direction);
private Func<T, object> GetOrderByExpression<T>(string sortColumn)
{
Func<T, object> orderByExpr = null;
if (!String.IsNullOrEmpty(sortColumn))
{
Type sponsorResultType = typeof(T);
if (sponsorResultType.GetProperties().Any(prop => prop.Name == sortColumn))
{
System.Reflection.PropertyInfo pinfo = sponsorResultType.GetProperty(sortColumn);
orderByExpr = (data => pinfo.GetValue(data, null));
}
}
return orderByExpr;
}
public List<T> OrderByDir<T>(IEnumerable<T> source, string dir, Func<T, object> OrderByColumn)
{
return dir.ToUpper() == "ASC" ? source.OrderBy(OrderByColumn).ToList() : source.OrderByDescending(OrderByColumn).ToList();``
}
// Call the code like below
var orderByExpression= GetOrderByExpression<SearchResultsType>(sort);
var data = OrderByDir<SponsorSearchResults>(resultRecords, SortDirectionString, orderByExpression);
This doesn't let you pass a string, as you asked for in your question, but it might still work for you.
The OrderByDescending method takes a Func<TSource, TKey>, so you can rewrite your function this way:
List<Student> QueryStudents<TKey>(Func<Student, TKey> orderBy)
{
return existingStudents.OrderByDescending(orderBy).ToList();
}
There are other overloads for OrderByDescending as well that take a Expression<Func<TSource, TKey>>, and/or a IComparer<TKey>. You could also look into those and see if they provide you anything of use.
The only solution that worked for me was posted here https://gist.github.com/neoGeneva/1878868 by neoGeneva.
I will re-post his code because it works well and I wouldn't want it to be lost in the interwebs!
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
{
if (source == null)
throw new ArgumentNullException("source", "source is null.");
if (string.IsNullOrEmpty(sortExpression))
throw new ArgumentException("sortExpression is null or empty.", "sortExpression");
var parts = sortExpression.Split(' ');
var isDescending = false;
var propertyName = "";
var tType = typeof(T);
if (parts.Length > 0 && parts[0] != "")
{
propertyName = parts[0];
if (parts.Length > 1)
{
isDescending = parts[1].ToLower().Contains("esc");
}
PropertyInfo prop = tType.GetProperty(propertyName);
if (prop == null)
{
throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, tType.Name));
}
var funcType = typeof(Func<,>)
.MakeGenericType(tType, prop.PropertyType);
var lambdaBuilder = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
.MakeGenericMethod(funcType);
var parameter = Expression.Parameter(tType);
var propExpress = Expression.Property(parameter, prop);
var sortLambda = lambdaBuilder
.Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });
var sorter = typeof(Queryable)
.GetMethods()
.FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { tType, prop.PropertyType });
return (IQueryable<T>)sorter
.Invoke(null, new object[] { source, sortLambda });
}
return source;
}
Add the nugget package Dynamite to your code
Add the namespace Dynamite.Extensions
Eg : using Dynamite.Extensions;
Give Order by query like any SQL query
Eg : students.OrderBy(" City DESC, Address").ToList();
To extend the response of #Icarus: if you want to sort by two fields I could perform the following function (for one field the response of Icarius works very well).
public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField1, string SortField2, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var body = GetBodyExp(SortField1, SortField2, param);
var exp = Expression.Lambda(body, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
This is the function that the body returns for the lambda expression, it works with string and int, but it is enough to add more types to make it work according to the need of each programmer
public static NewExpression GetBodyExp(string field1, string field2, ParameterExpression Parametro)
{
// SE OBTIENE LOS NOMBRES DE LOS TIPOS DE VARIABLE
string TypeName1 = Expression.Property(Parametro, field1).Type.Name;
string TypeName2 = Expression.Property(Parametro, field2).Type.Name;
// SE DECLARA EL TIPO ANONIMO SEGUN LOS TIPOS DE VARIABLES
Type TypeAnonymous = null;
if (TypeName1 == "String")
{
string var1 = "0";
if (TypeName2 == "Int32")
{
int var2 = 0;
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
if (TypeName2 == "String")
{
string var2 = "0";
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
}
if (TypeName1 == "Int32")
{
int var1 = 0;
if (TypeName2 == "Int32")
{
string var2 = "0";
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
if (TypeName2 == "String")
{
string var2 = "0";
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
}
//se declaran los TIPOS NECESARIOS PARA GENERAR EL BODY DE LA EXPRESION LAMBDA
MemberExpression[] args = new[] { Expression.PropertyOrField(Parametro, field1), Expression.PropertyOrField(Parametro, field2) };
ConstructorInfo CInfo = TypeAnonymous.GetConstructors()[0];
IEnumerable<MemberInfo> a = TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property);
//BODY
NewExpression body = Expression.New(CInfo, args, TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property));
return body;
}
to use it the following is done
IQueryable<MyClass> IqMyClass= context.MyClass.AsQueryable();
List<MyClass> ListMyClass= IqMyClass.OrderByDynamic("UserName", "IdMyClass", true).ToList();
if there is a better way to do this, it would be great if they share it
I managed to solve it thanks to: How can I make a Multiple property lambda expression with Linq
New Answer : this is a more complete answer that supports multiple columns for order by like SQL. Example : .OrderBy("FirstName,Age DESC") :
namespace Utility;
public static class QueryExtension
{
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc, bool isThenBy = false)
{
string command = isThenBy ? (desc ? "ThenByDescending" : "ThenBy") : (desc ? "OrderByDescending" : "OrderBy");
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string sqlOrderByList)
{
var ordebyItems = sqlOrderByList.Trim().Split(',');
IQueryable<TEntity> result = source;
bool useThenBy = false;
foreach (var item in ordebyItems)
{
var splt = item.Trim().Split(' ');
result = result.OrderBy(splt[0].Trim(), (splt.Length > 1 && splt[1].Trim().ToLower() == "desc"), useThenBy);
if (useThenBy)
useThenBy = true;
}
return result;
}
}
The second function iterates over orderby columns and uses the first one.
Use it like this :
using Utility;
...
public void MyMethod()
{
var query = _dbContext.Person.AsQueryable();
query.OrderBy("FirstName,Age DESC");
}
I'm way late to the party but none of these solutions worked for me. I was eager to try System.Linq.Dynamic, but I couldn't find that on Nuget, maybe depreciated? Either way...
Here is a solutions I came up with. I needed to dynamically use a mixture of OrderBy, OrderByDescending and OrderBy > ThenBy.
I simply created an extension method for my list object, a bit hacky I know... I wouldn't recommend this if it were something I was doing a lot of, but it's good for a one off.
List<Employee> Employees = GetAllEmployees();
foreach(Employee oEmployee in Employees.ApplyDynamicSort(eEmployeeSort))
{
//do stuff
}
public static IOrderedEnumerable<Employee> ApplyDynamicSort(this List<Employee> lEmployees, Enums.EmployeeSort eEmployeeSort)
{
switch (eEmployeeSort)
{
case Enums.EmployeeSort.Name_ASC:
return lEmployees.OrderBy(x => x.Name);
case Enums.EmployeeSort.Name_DESC:
return lEmployees.OrderByDescending(x => x.Name);
case Enums.EmployeeSort.Department_ASC_Salary_DESC:
return lEmployees.OrderBy(x => x.Department).ThenByDescending(y => y.Salary);
default:
return lEmployees.OrderBy(x => x.Name);
}
}

Better way to Sort a List by any property

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.

Creating Sorting Func<IQueryable<T>, IOrderedQueryable<T>>?

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

Categories

Resources