Related
is there a short way,
I don't want to use "if state" for every situation
what do you think
Description : type of query is IQueryable
public class OrderFilter{
public string SearchValue { get => _searchValue.ToLower(); set => _searchValue = value; }
public string[] SearchColumns { get; set; }
}
if (!string.IsNullOrWhiteSpace(orderFilter.SearchValue))
if (orderFilter.SearchColumns.Contains("warehouse"))
query = query.Where(x => x.Warehouse.Description.Contains(orderFilter.SearchValue));
if (orderFilter.SearchColumns.Contains("date"))
query = query.Where(x => x.Date.Contains(orderFilter.SearchValue));
if (orderFilter.SearchColumns.Contains("warehouse") && orderFilter.SearchColumns.Contains("date"))
query = query.Where(x => x.OrderNo.Contains(orderFilter.SearchValue)
|| x.Warehouse.Description.Contains(orderFilter.SearchValue)
|| x.Client.Description.Contains(orderFilter.SearchValue)
|| x.Date.ToString().Contains(orderFilter.SearchValue)
|| x.Salesman.Username.Contains(orderFilter.SearchValue)
);
}
Access nested properties with dynamic lambda using Linq.Expression
found the answer I was looking for
public class LambdaHelper
{
public Expression<Func<TSource, bool>> MultiSearchOrder<TSource>(string[] columns, string value)
{
ParameterExpression parameter = Expression.Parameter(typeof(TSource), "x"); // x=> . ... demektir. parametre
MethodInfo containsMethod = typeof(String).GetMethod("Contains", new Type[] { typeof(String) });
Expression dynamiclambda = null;
MethodCallExpression call = null;
foreach (var propertyName in columns)
{
MemberExpression propertyAccess = NestedExpressionProperty(parameter, propertyName);
call = Expression.Call(propertyAccess, containsMethod, Expression.Constant(value));
if (null == dynamiclambda)
{
dynamiclambda = call;
}
else
{
dynamiclambda = Expression.Or(dynamiclambda, call);
}
}
Expression<Func<TSource, bool>> predicate = Expression.Lambda<Func<TSource, bool>>(dynamiclambda, parameter);
return predicate;
}
private MemberExpression NestedExpressionProperty(Expression expression, string propertyName)
{
string[] parts = propertyName.Split('.');
int partsL = parts.Length;
return (partsL > 1)
?
Expression.Property(
NestedExpressionProperty(
expression,
parts.Take(partsL - 1)
.Aggregate((a, i) => a + "." + i)
),
parts[partsL - 1])
:
Expression.Property(expression, propertyName);
}
I think you add one property (SearchKey) that property contains of all properties value that used for search in writing time and you using this property instead of OR for example :
savingTime: SearhKey="OrderNo.value Warehouse.Description.Value ...."
Searching Time:
if (!string.IsNullOrWhiteSpace(orderFilter.SearchValue))
{
query = query.Where(x =>
x.SearchKey.Contains(orderFilter.SearchValue) );
}
you can do an extension o OrderFilter and reuse it :
public static bool HasAny<TSource>(this OrderFilter filter, IEnumerable<TSource> source)
{
if(filter == null)
throw new ArgumentNullException(nameof(filter));
if(filter.SearchColumns == null)
throw new ArgumentNullException(nameof(filter.SearchColumns));
if(string.IsNullOrWhiteSpace(filter.SearchValue))
throw new ArgumentNullException(nameof(filter.SearchValue));
if(source == null)
throw new ArgumentNullException(nameof(source));
var properties = typeof(TSource).GetProperties().Where(x=> filter.SearchColumns.Any(s=> s.IndexOf(x.Name, StringComparison.InvariantCultureIgnoreCase) > 0));
foreach(var item in source)
{
foreach(var property in properties)
{
var value = property.GetValue(item)?.ToString();
if(value?.Equals(filter.SearchValue , StringComparison.InvariantCultureIgnoreCase) == true)
{
return true;
}
}
}
return false;
}
now you can do this :
var isValueExists = orderFilter.HasAny(query);
For a generic filter I am implementing, I need to modify the following to accept the column name which will be searched in the Where method, something like:
public IQueryable<TEntity> GetEntities(string val)
{
TEntity entity = _DbContext.Set<TEntity>()
.Where(e => e.Col1.Contains(val));
return entities;
}
to be changed to
public IQueryable<TEntity> GetEntities(string val, string colName)
{
TEntity entity = _DbContext.Set<TEntity>()
.WhereContains(val, colName);
return entities;
}
colName is the name of a string column.
I looked at https://blog.jeremylikness.com/blog/dynamically-build-linq-expressions/ but could not modify the example there for my needs. The answer should be in the form of
public static IQueryable<TEntity> WhereContains<TEntity>(this IQueryable<TEntity> query, string value, string colName)
where TEntity : class
{
...
...
}
But I cant make it work...
OK, found a good reference and was able to modify it:
public static IQueryable<T> TextFilter<T>(IQueryable<T> source, string[] colNames, string[] terms)
{
if (colNames.Length == 0) return source;
// T is a compile-time placeholder for the element type of the query.
Type elementType = typeof(T);
// Get all the properties on this specific type for colNames.
List<PropertyInfo> props = new List<PropertyInfo>();
for (int i = 0; i < colNames.Length; i++)
{
PropertyInfo prop = elementType.GetProperties().Where(x => x.PropertyType == typeof(string) && x.Name.ToLower() == colNames[i].ToLower()).FirstOrDefault();
if (prop == null) { return source; }
props.Add(prop);
}
// Get the right overload of String.Contains. Can be replaced e.g. with "Contains"
MethodInfo containsMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!;
// Create a parameter for the expression tree:
// the 'x' in 'x => x.PropertyName.Contains("term")'
// The type of this parameter is the query's element type
ParameterExpression prm = Expression.Parameter(elementType);
// Map each property to an expression tree node
List<Expression> expressions = new List<Expression>();
for (int i = 0; i < colNames.Length; i++)
{
expressions.Add(
Expression.Call(
Expression.Property(
prm,
props.ElementAt(i)
),
containsMethod,
Expression.Constant(terms[i])
)
);
}
// Combine all the resultant expression nodes using ||
Expression body = expressions
.Aggregate(
(prev, current) => Expression.And(prev, current)
);
// Wrap the expression body in a compile-time-typed lambda expression
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, prm);
// Because the lambda is compile-time-typed (albeit with a generic parameter), we can use it with the Where method
return source.Where(lambda);
}
I was not able to make this work as an extension, so calling it is done with:
var qry= QueryableExtensions.TextFilter(_crudApiDbContext.Set<TEntity>()
.Where(entity => entity.someColumn==someValue),
filters.Keys.ToArray(), filters.Values.ToArray());
List<TEntity> entities = await qry
.Skip(pageSize * (page - 1)).Take(pageSize).ToListAsync();
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);
}
}
I have a method, GetSearchExpression, defined as:
private Expression<Func<T, bool>> GetSearchExpression(
string targetField, ExpressionType comparison, object value, IEnumerable<EnumerableResultQualifier> qualifiers = null);
At a high level, the method takes in a Field or Property (such as Order.Customer.Name), a comparison type (like Expression.Equals), and a value (like "Billy"), then returns a lambda expression suitable for input to a Where statement o => o.Customer.Name == "Billy"}.
Recently, I discovered an issue. Sometimes, the field I need is actually the field of an item in a collection (like Order.StatusLogs.First().CreatedDate).
I feel like that should be easy. The code that creates the left side of the expression (above, o => o.Customer.Name) is as follows:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
//turn "Order.Customer.Name" into List<string> { "Customer", "Name" }
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
//loop through each part and grab the specified field or property
foreach (var part in deQualifiedFieldName)
left = Expression.PropertyOrField(left == null ? param : left, part);
It seems like I should be able to revise this to check if the field/property exists, and if not, try to call a method by that name instead. It would look like this:
var param = Expression.Parameter(typeof(T), "t");
Expression left = null;
var deQualifiedFieldName = DeQualifyFieldName(targetField, typeof(T));
var currentType = typeof(T);
foreach (var part in deQualifiedFieldName)
{
//this gets the Type of the current "level" we're at in the hierarchy passed via TargetField
currentType = SingleLevelFieldType(currentType, part);
if (currentType != null) //if the field/property was found
{
left = Expression.PropertyOrField(left == null ? param : left, part);
}
else
{ //if the field or property WASN'T found, it might be a method
var method = currentType.GetMethod(part, Type.EmptyTypes); //doesn't accept parameters
left = Expression.Call(left, method);
currentType = method.ReturnType;
}
}
The problem is that statement near the end (var method currentType.GetMethod(part, Type.EmptyTypes);). Turns out "First" and "Last" don't exist for IEnumerable objects, so I get a null exception when I try to use my Method object. In fact, the only way I can EVER them to show up in a GetMethod() call is by calling typeof(Enumerable).GetMethod(). That's useless of course, because then I get a static method in return rather than the instance method I need.
As a side-note: I tried using the static method, but Entity Framework throws a fit and won't accept it as part of the lambda.
I need help getting the instance MethodInfo of IEnumerable.First() & Last(). Please help!
My first attempt would be to identify if the instance is Enumerable<T> and treat the member name as method instead of a property/field like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
return Expression.Call(typeof(Enumerable), memberName, enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and try use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.First.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
The possible methods are First, FirstOrDefault, Last, LastOrDefault, Singe and SingleOrDefault.
But then you'll find that from the above methods only FirstOrDefault is supported in EF predicates.
Hence we can hardcode that call for collection types and do not include it in the accessors like this
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate2<T>(
string memberPath, ExpressionType comparison, object value)
{
var param = Expression.Parameter(typeof(T), "t");
var right = Expression.Constant(value);
var left = memberPath.Split('.').Aggregate((Expression)param, (target, memberName) =>
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var enumerableType = target.Type.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
target = Expression.Call(typeof(Enumerable), "FirstOrDefault", enumerableType.GetGenericArguments(), target);
}
return Expression.PropertyOrField(target, memberName);
});
var body = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
and use it as follows
var predicate = ExpressionUtils.MakePredicate<Order>(
"StatusLogs.CreatedDate", ExpressionType.GreaterThanOrEqual, new DateTime(2016, 1, 1));
P.S. While this will work, it might not produce the intended result. IEnumerable<T> navigation property means one-to-many relationship and assuming that the condition should apply only for the first (whatever that means in database, it's rather random) element does not make much sense. I would rather imply Any and try to build expression like this in the above case
t => t.StatusLogs.Any(s => s.CreatedDate >= new DateTime(2016, 1, 1))
or support FirstOrDefault, Any, All, (eventually Count, Sum, Min, Max) and handle them differently inside the builder.
Still IMO for collections Any is the most logical equivalent of the single entity criteria.
But all that will be another story (question).
UPDATE: Initially I was thinking to stop here, but for the sake of completeness, here is a sample implementation of the Any concept:
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> MakePredicate<T>(string memberPath, ExpressionType comparison, object value)
{
return (Expression<Func<T, bool>>)MakePredicate(
typeof(T), memberPath.Split('.'), 0, comparison, value);
}
static LambdaExpression MakePredicate(Type targetType, string[] memberNames, int index, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(targetType, targetType.Name.ToCamel());
Expression target = parameter;
for (int i = index; i < memberNames.Length; i++)
{
if (typeof(IEnumerable).IsAssignableFrom(target.Type))
{
var itemType = target.Type.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
var itemPredicate = MakePredicate(itemType, memberNames, i, comparison, value);
return Expression.Lambda(
Expression.Call(typeof(Enumerable), "Any", new[] { itemType }, target, itemPredicate),
parameter);
}
target = Expression.PropertyOrField(target, memberNames[i]);
}
if (value != null && value.GetType() != target.Type)
value = Convert.ChangeType(value, target.Type);
return Expression.Lambda(
Expression.MakeBinary(comparison, target, Expression.Constant(value)),
parameter);
}
static string ToCamel(this string s)
{
if (string.IsNullOrEmpty(s) || char.IsLower(s[0])) return s;
if (s.Length < 2) return s.ToLower();
var chars = s.ToCharArray();
chars[0] = char.ToLower(chars[0]);
return new string(chars);
}
}
so for this sample model
public class Foo
{
public ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public ICollection<Baz> Bazs { get; set; }
}
public class Baz
{
public ICollection<Detail> Details { get; set; }
}
public class Detail
{
public int Amount { get; set; }
}
the sample expression
var predicate = ExpressionUtils.MakePredicate<Foo>(
"Bars.Bazs.Details.Amount", ExpressionType.GreaterThan, 1234);
produces
foo => foo.Bars.Any(bar => bar.Bazs.Any(baz => baz.Details.Any(detail => detail.Amount > 1234)))
What you are possibly looking for is System.Linq.Enumerable.First<T>(this IEnumerable<T> source) etc, so: start at typeof(System.Linq.Enumerable) and work from there. Note: you mention IEnumerable<T>, but it is possible that you actually mean IQueryable<T>, in which case you want Queryable.First<T>(this IQueryable<T> source) etc. Maybe this difference (between Enumerable and Queryable) is why EF "throws a fit".
Thank you to Marc and Ivan for their input. They deserve credit as without their help I would have spent much longer finding a solution. However, as neither answer solved the issue I was having, I'm posting the solution that worked for me (successfully applying criteria as well as successfully querying against an EF data source):
private Expression<Func<T, bool>> GetSearchExpression(string targetField, ExpressionType comparison, object value, string enumMethod)
{
return (Expression<Func<T, bool>>)MakePredicate(DeQualifyFieldName(targetField, typeof(T)), comparison, value, enumMethod);
}
private LambdaExpression MakePredicate(string[] memberNames, ExpressionType comparison, object value, string enumMethod = "Any")
{
//create parameter for inner lambda expression
var parameter = Expression.Parameter(typeof(T), "t");
Expression left = parameter;
//Get the value against which the property/field will be compared
var right = Expression.Constant(value);
var currentType = typeof(T);
for (int x = 0; x < memberNames.Count(); x++)
{
string memberName = memberNames[x];
if (FieldExists(currentType, memberName))
{
//assign the current type member type
currentType = SingleLevelFieldType(currentType, memberName);
left = Expression.PropertyOrField(left == null ? parameter : left, memberName);
//mini-loop for non collection objects
if (!currentType.IsGenericType || (!(currentType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
currentType.GetGenericTypeDefinition() == typeof(ICollection<>))))
continue;
///Begin loop for collection objects -- this section can only run once
//get enum method
if (enumMethod.Length < 2) throw new Exception("Invalid enum method target.");
bool negateEnumMethod = enumMethod[0] == '!';
string methodName = negateEnumMethod ? enumMethod.Substring(1) : enumMethod;
//get the interface sub-type
var itemType = currentType.GetInterfaces()
.Single(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.GetGenericArguments()[0];
//generate lambda for single item
var itemPredicate = MakeSimplePredicate(itemType, memberNames[++x], comparison, value);
//get method call
var staticMethod = typeof(Enumerable).GetMember(methodName).OfType<MethodInfo>()
.Where(m => m.GetParameters().Length == 2)
.First()
.MakeGenericMethod(itemType);
//generate method call, then break loop for return
left = Expression.Call(null, staticMethod, left, itemPredicate);
right = Expression.Constant(!negateEnumMethod);
comparison = ExpressionType.Equal;
break;
}
}
//build the final expression
var binaryExpression = Expression.MakeBinary(comparison, left, right);
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
static LambdaExpression MakeSimplePredicate(Type inputType, string memberName, ExpressionType comparison, object value)
{
var parameter = Expression.Parameter(inputType, "t");
Expression left = Expression.PropertyOrField(parameter, memberName);
return Expression.Lambda(Expression.MakeBinary(comparison, left, Expression.Constant(value)), parameter);
}
private static Type SingleLevelFieldType(Type baseType, string fieldName)
{
Type currentType = baseType;
MemberInfo match = (MemberInfo)currentType.GetField(fieldName) ?? currentType.GetProperty(fieldName);
if (match == null) return null;
return GetFieldOrPropertyType(match);
}
public static Type GetFieldOrPropertyType(MemberInfo field)
{
return field.MemberType == MemberTypes.Property ? ((PropertyInfo)field).PropertyType : ((FieldInfo)field).FieldType;
}
/// <summary>
/// Remove qualifying names from a target field. For example, if targetField is "Order.Customer.Name" and
/// targetType is Order, the de-qualified expression will be "Customer.Name" split into constituent parts
/// </summary>
/// <param name="targetField"></param>
/// <param name="targetType"></param>
/// <returns></returns>
public static string[] DeQualifyFieldName(string targetField, Type targetType)
{
return DeQualifyFieldName(targetField.Split('.'), targetType);
}
public static string[] DeQualifyFieldName(string[] targetFields, Type targetType)
{
var r = targetFields.ToList();
foreach (var p in targetType.Name.Split('.'))
if (r.First() == p) r.RemoveAt(0);
return r.ToArray();
}
I included related methods in case someone actually needs to sort through this at some point. :)
Thanks again!
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);
}
}