I'm using a generic method to sort an IQueryable collection which uses the Queryable.OrderBy and LambdaExpression methods:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, IEnumerable<SortParameter> sortParameters)
{
IOrderedQueryable<T> sourceOrderedQueryable = sortParameters[0].SortDirection == ListSortDirection.Ascending
? Queryable.OrderBy(source, (dynamic)CreateExpression<T>(sortParameter[0].ParameterName))
: Queryable.OrderByDescending(source, (dynamic)CreateExpression<T>(sortParameter[0].ParameterName));
// ... Same with Queryable.ThenBy
return sourceOrderedQueryable;
}
private static LambdaExpression CreateExpression<T>(string propertyName)
{
var modelParameter = Expression.Parameter(typeof(T), "t");
Expression body = Expression.PropertyOrField(modelParameter, sortParameter);
return Expression.Lambda(body, modelParameter);
}
query.OrderBy(sortParameters) // for ProductId sortParameter (string type in db) should sort as int
This works fine but I have to parse one column from the sorted model from string to int, something like:
OrderBy(x => Convert.ToInt32(x.ProductId))
I have no idea how to integrate the above conversion into this generic mechanism and the lambda expression for a specific case. Is it possible for this mechanism to converting one column (property) type?
I wrote an extension method to IQueryable OrderBy (and OrderByDescending, ThenBy and ThenByDescending), which all call my function CreateExpression to get the expression for sorting.
Problem is if someone calls this method with an invalid parameter, for example a string which is not a member name in the object type being sorted. That's why I check if pi is not null. Now I need to return something other than null if the parameter was invalid and pi is null, but I'm not quite sure how to do that. I would like to return an expression without sorting, is that possible and how would I do that?
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string orderBy)
{
if (!string.IsNullOrWhiteSpace(orderBy))
{
var resultExp = CreateExpression(source, "OrderBy", orderBy);
return source.Provider.CreateQuery<T>(resultExp);
}
else
{
return source;
}
}
private static MethodCallExpression CreateExpression<T>(IQueryable<T> source, string methodName, string orderBy)
{
if (methodName != "OrderBy" && methodName != "OrderByDescending" && methodName != "ThenBy" && methodName != "ThenByDescending")
methodName = "OrderBy";
if (!string.IsNullOrWhiteSpace(orderBy))
{
PropertyInfo pi = typeof(T).GetProperty(orderBy);
if (pi != null)
{
var parameter = Expression.Parameter(typeof(T), "p");
MemberExpression me = Expression.MakeMemberAccess(parameter, pi);
var orderByExp = Expression.Lambda(me, parameter);
return Expression.Call(typeof(Queryable), methodName,
new Type[] { typeof(T), pi.PropertyType }, source.Expression, Expression.Quote(orderByExp));
}
}
//todo
return null;
}
Potential solution would be to use a property expression like this. Pass a property selector into your method, instead of a string:
Expression<Func<T, TProperty>> propertySelector
Inside of your method you can get the name of your property and use it just like in your code:
private static string GetPropertyName<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertySelector)
{
MemberExpression member = propertySelector.Body as MemberExpression;
PropertyInfo propInfo = member.Member as PropertyInfo;
return propInfo.Name;
}
Your method signature would look like this:
public static IQueryable<T> OrderBy<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> propertySelector)
And you could call it this way:
collection.OrderBy(p => p.UserName)
You'll get static typing, and you can remove the magic string.
Otherwise, than that - I'd throw an InvalidOperationException, given it's an invalid situation and the user of your code should be informed about it with an exception - rather than the code silently failing.
Since you have written your extension method to work with any type (you have no constraints), the only option you have is to throw an exception if the user has provided you with a property name that does not exist. There is not much else you can do.
However, if you apply a simple constraint to T, then you can use that to provide a default sort in the case wherein the provided property does not exist. Here is how:
Create an interface and have all your Ts implement the interface:
public interface IOrderable
{
int Id { get; }
}
Change your extension and apply a constraint to it. Then if you do not find the provided property name, return an expression which will sort on the Id column by default:
private static MethodCallExpression CreateExpression<T>(IQueryable<T> source, string methodName, string orderBy)
where T : IOrderable
{
// your code...
//here is the todo
// Here we are returning an expression which will sort on the
// Id column by default
var parameterDef = Expression.Parameter(typeof(T), "p");
PropertyInfo piDef = typeof(T).GetProperty(nameof(IOrderable.Id));
MemberExpression meDef = Expression.MakeMemberAccess(parameterDef, piDef);
var orderByExpDef = Expression.Lambda(meDef, parameterDef);
return Expression.Call(typeof(Queryable), methodName,
new Type[]
{
typeof(T), piDef.PropertyType
},
source.Expression, Expression.Quote(orderByExpDef));
}
I have several autocomplete actions, one of them is listed below. Instead of writing different predicates for each autocomplete Where method, I have created an autoCompletePredicate. Since I have multiple autocompletes I am using Reflection to get the Property which is required for that specific AutoComplete and use that Property in my autoCompletePredicate.
I have following code which is working alright.
static string param1, param2;
static PropertyInfo[] properties;
static PropertyInfo prop1, prop2;
public IHttpActionResult GetAutComplete(string term, string dependent)
{
int pagerSize = 10;
properties = new MyObject().GetType().GetProperties();
prop1 = properties.Where(p => p.Name.ToUpper().Equals("PROP1")).FirstOrDefault();
prop2 = properties.Where(p => p.Name.ToUpper().Equals("PROP2")).FirstOrDefault();
param1 = term;
param2 = dependent;
return Json(context.MyObject.Where(autoCompletePredicate).Select(r => new { label = r.PROP1 }).Distinct().OrderBy(r => r.label).Take(pagerSize).ToList());
}
Func<MyObject, int, bool> autoCompletePredicate = (GF, index) =>
{
bool isFound = false;
string term, dependent;
term = prop1.GetValue(GF).ToString();
dependent = prop2.GetValue(GF).ToString();
var termFound = term.Contains(param1.ToUpper());
var dependentFound = String.IsNullOrEmpty(param2) ? true : dependent.Contains(param2.ToUpper());
isFound = termFound && dependentFound;
return isFound;
};
How can I change this code into Expression. I tried below code which compiled fine but at runtime I got the following error
public static Expression<Func<MyObject, bool>> AutoCompleteExpression()
{
return r => prop1.GetValue(r).ToString().Contains(param1.ToUpper()) && (String.IsNullOrEmpty(param2) ? true : prop2.GetValue(r).ToString().Contains(param2.ToUpper()));
}
"LINQ to Entities does not recognize the method 'System.Object
GetValue(System.Object)' method, and this method cannot be translated
into a store expression."
I looked at the following post which makes absolute sense, but I am not sure how I can use that example in my scenario (which is dynamically finding properties using Reflection).
Also, what I would like to know what can be advantage of using Expression vs Func (specially in my case)
You are trying to trying to execute Contains method on string and want that to be represented in the ExpressionTrees, following is the code you need:
Create String Extension method - Contains: (Case Insensitive)
public static class StringExtensions
{
public static bool Contains(this string source, string toCheck)
{
return source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
Create the AutoCompleteExpression method as follows, it returns Func<MyObject, bool>:
public static Func<MyObject, bool> AutoCompleteExpression()
{
// Create ParameterExpression
ParameterExpression parameterType = Expression.Parameter(typeof(MyObject), "object");
// Create MemberExpression for Columns
MemberExpression typeColumnProp1 = Expression.Property(parameterType, "PROP1");
MemberExpression typeColumnProp2 = Expression.Property(parameterType, "PROP2");
// Create MethoIndo
MethodInfo containsMethodInfo = typeof(StringExtensions).GetMethod("Contains",new[] { typeof(string), typeof(string) },null);
// Create ConstantExpression values
ConstantExpression constant1 = Expression.Constant(param1, typeof(string));
ConstantExpression constant2 = Expression.Constant(param2, typeof(string));
// Expression for calling methods
MethodCallExpression expression1 = Expression.Call(null, containsMethodInfo, typeColumnProp1, constant1);
MethodCallExpression expression2 = Expression.Call(null, containsMethodInfo, typeColumnProp2, constant2);
// Combine `MethodCallExpression` to create Binary Expression
BinaryExpression resultExpression = Expression.And(expression1,expression2);
// Compile Expression tree to fetch `Func<MyObject, bool>`
return Expression.Lambda<Func<MyObject, bool>>(resultExpression, parameterType).Compile();
}
It is possible to add lot more flexibility by defining custom extension methods and combining expressions using And / Or
After a good dose of Googling and trying some things and not finding/getting the desired result I decided to post this question.
I have a custom made OrderBy extension method and now when performing the OrderBy operation I'd like to pass an AlphanumComparator like this:
return divergences.OrderBy(sort, new AlphanumComparator());
Here's the extension method:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection,
GridSortOptions sortOptions, AlphanumComparator comparer = null)
{
if (string.IsNullOrEmpty(sortOptions.Column))
{
return collection;
}
Type collectionType = typeof(T);
ParameterExpression parameterExpression = Expression.Parameter(collectionType, "p");
Expression seedExpression = parameterExpression;
Expression aggregateExpression = sortOptions.Column.Split('.').Aggregate(seedExpression, Expression.Property);
MemberExpression memberExpression = aggregateExpression as MemberExpression;
if (memberExpression == null)
{
throw new NullReferenceException(string.Format("Unable to cast Member Expression for given path: {0}.", sortOptions.Column));
}
LambdaExpression orderByExp = Expression.Lambda(memberExpression, parameterExpression);
const string orderBy = "OrderBy";
const string orderByDesc = "OrderByDescending";
Type childPropertyType = ((PropertyInfo)(memberExpression.Member)).PropertyType;
string methodToInvoke = sortOptions.Direction == MvcContrib.Sorting.SortDirection.Ascending ? orderBy : orderByDesc;
MethodCallExpression orderByCall;
orderByCall = Expression.Call(typeof(Queryable), methodToInvoke, new[] { collectionType, childPropertyType }, collection.Expression, Expression.Quote(orderByExp));
if(comparer != null)
{
// How can I pass the comparator to the OrderBy MethodCallExpression?
// Using the standard LINQ OrderBy, we can do this:
// elements.OrderBy(e => e.Index, new AlphanumComparator())
}
return collection.Provider.CreateQuery<T>(orderByCall);
}
See the comment in the code where I think I should pass the IComparer... how could I approach this?
I had to approach this differently.
I was trying to create a generic OrderBy to be used with MvcContrib Grid, but passing the IComparer to that custom OrderBy expression did not work as I imagined it would work.
So I created this helper that receives a string in dot notation like Element1.Standard.Chapter.Manual.Name and then returns an Expression<Func<T, string>>:
public static Func<T, string> CreateSelectorExpression<T>(string propertyName) where T : class
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T));
Expression aggregateExpression = propertyName.Split('.').
Aggregate(parameterExpression as Expression, Expression.Property) as MemberExpression;
LambdaExpression exp = Expression.Lambda(aggregateExpression, parameterExpression);
return (Func<T, string>)exp.Compile();
}
This expression typed to T (in this case Divergence object type) can then be passed (see func.Invoke) to the standard LINQ OrderBy operator where I can also pass the custom IComparer AlphanumComparator like this:
if (sort.Column.Contains("Index"))
{
var func = Helpers.ExtensionMethods.CreateSelectorExpression<Divergence>(sort.Column);
if (sort.Direction == SortDirection.Ascending)
{
return divergences.OrderBy(func, new AlphanumComparator());
}
else
{
return divergences.OrderByDescending(func, new AlphanumComparator());
}
}
This involved a little bit more work but solved the problem in a generic fashion the way I wanted it to be.
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 1 year ago.
What's the simplest way to code against a property in C# when I have the property name as a string? For example, I want to allow the user to order some search results by a property of their choice (using LINQ). They will choose the "order by" property in the UI - as a string value of course. Is there a way to use that string directly as a property of the linq query, without having to use conditional logic (if/else, switch) to map the strings to properties. Reflection?
Logically, this is what I'd like to do:
query = query.OrderBy(x => x."ProductId");
Update:
I did not originally specify that I'm using Linq to Entities - it appears that reflection (at least the GetProperty, GetValue approach) does not translate to L2E.
I would offer this alternative to what everyone else has posted.
System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");
query = query.OrderBy(x => prop.GetValue(x, null));
This avoids repeated calls to the reflection API for obtaining the property. Now the only repeated call is obtaining the value.
However
I would advocate using a PropertyDescriptor instead, as this will allow for custom TypeDescriptors to be assigned to your type, making it possible to have lightweight operations for retrieving properties and values. In the absence of a custom descriptor it will fall back to reflection anyhow.
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");
query = query.OrderBy(x => prop.GetValue(x));
As for speeding it up, check out Marc Gravel's HyperDescriptor project on CodeProject. I've used this with great success; it's a life saver for high-performance data binding and dynamic property operations on business objects.
I'm a little late to the party, however, I hope this can be of some help.
The problem with using reflection is that the resulting Expression Tree will almost certainly not be supported by any Linq providers other than the internal .Net provider. This is fine for internal collections, however this will not work where the sorting is to be done at source (be that SQL, MongoDb, etc.) prior to pagination.
The code sample below provides IQueryable extention methods for OrderBy and OrderByDescending, and can be used like so:
query = query.OrderBy("ProductId");
Extension Method:
public static class IQueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
{
return source.OrderByDescending(ToLambda<T>(propertyName));
}
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var propAsObject = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<T, object>>(propAsObject, parameter);
}
}
Regards, Mark.
I liked the answer from #Mark Powell, but as #ShuberFu said, it gives the error LINQ to Entities only supports casting EDM primitive or enumeration types.
Removing var propAsObject = Expression.Convert(property, typeof(object)); didn't work with properties that were value types, such as integer, as it wouldn't implicitly box the int to object.
Using Ideas from Kristofer Andersson and Marc Gravell I found a way to construct the Queryable function using the property name and have it still work with Entity Framework. I also included an optional IComparer parameter. Caution: The IComparer parameter does not work with Entity Framework and should be left out if using Linq to Sql.
The following works with Entity Framework and Linq to Sql:
query = query.OrderBy("ProductId");
And #Simon Scheurer this also works:
query = query.OrderBy("ProductCategory.CategoryId");
And if you are not using Entity Framework or Linq to Sql, this works:
query = query.OrderBy("ProductCategory", comparer);
Here is the code:
public static class IQueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "ThenBy", propertyName, comparer);
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer);
}
/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
IComparer<object> comparer = null)
{
var param = Expression.Parameter(typeof(T), "x");
var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);
return comparer != null
? (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param),
Expression.Constant(comparer)
)
)
: (IOrderedQueryable<T>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
methodName,
new[] { typeof(T), body.Type },
query.Expression,
Expression.Lambda(body, param)
)
);
}
}
Yes, I don't think there's another way than Reflection.
Example:
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));
Trying to recall exact syntax off the top of my head but I think that is correct.
Warning ⚠️
You just can use Reflection in case that data is in-memory. Otherwise, you will see some error like below when you work with Linq-2-EF, Linq-2-SQL, etc.
#Florin Vîrdol's comment
LINQ to Entities does not recognize the method 'System.Object
GetValue(System.Object)' method and this method cannot be translated
into a store expression.
Why 🤔
Because when you write code to provide a query to Linq query provider. It is first translated into an SQL statement and then executed on the database server.
(See image below, from https://www.tutorialsteacher.com/linq/linq-expression)
Solution ✅
By using Expression tree, you can write a generic method like this
public static IEnumerable<T> OrderDynamic<T>(IEnumerable<T> Data, string propToOrder)
{
var param = Expression.Parameter(typeof(T));
var memberAccess = Expression.Property(param, propToOrder);
var convertedMemberAccess = Expression.Convert(memberAccess, typeof(object));
var orderPredicate = Expression.Lambda<Func<T, object>>(convertedMemberAccess, param);
return Data.AsQueryable().OrderBy(orderPredicate).ToArray();
}
And use it like this
var result = OrderDynamic<Student>(yourQuery, "StudentName"); // string property
or
var result = OrderDynamic<Student>(yourQuery, "Age"); // int property
And it's also working with in-memory by converting your data into IQueryable<TElement> in your generic method return statement like this
return Data.AsQueryable().OrderBy(orderPredicate).ToArray();
See the image below to know more in-depth.
Demo on dotnetfiddle
More productive than reflection extension to dynamic order items:
public static class DynamicExtentions
{
public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
{
var param = Expression.Parameter(typeof(Tobj), "value");
var getter = Expression.Property(param, propertyName);
var boxer = Expression.TypeAs(getter, typeof(object));
var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();
return getPropValue(self);
}
}
Example:
var ordered = items.OrderBy(x => x.GetPropertyDynamic("ProductId"));
Also you may need to cache complied lambas(e.g. in Dictionary<>)
Reflection is the answer!
typeof(YourType).GetProperty("ProductId").GetValue(theInstance);
There's lots of things you can do to cache the reflected PropertyInfo, check for bad strings, write your query comparison function, etc., but at its heart, this is what you do.
Also Dynamic Expressions can solve this problem.
You can use string-based queries through LINQ expressions that could have been dynamically constructed at run-time.
var query = query
.Where("Category.CategoryName == #0 and Orders.Count >= #1", "Book", 10)
.OrderBy("ProductId")
.Select("new(ProductName as Name, Price)");
I think we can use a powerful tool name Expression an in this case use it as an extension method as follows:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, bool descending)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp =
Expression.Call(typeof(Queryable), (descending ? "OrderByDescending" : "OrderBy"),
new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(resultExp);
}