C # - code for ordering by property using the property name as a string

What is the easiest way to encode a property in C # when I have the property name as a string? For example, I want to allow a user to order some search results of their choice (using LINQ). They themselves choose the order by property in the user interface β€” as a string value. Is there a way to use this string directly as a linq query property, without having to use conditional logic (if / else, switch) to map strings to properties. Reflection?

Logically, this is what I would like to do:

query = query.OrderBy(x => x."ProductId"); 

Update: I did not initially indicate that I use Linq for Entities - it seems that the reflection (at least the GetProperty, GetValue method) does not translate to L2E.

+65
c # linq linq-to-entities
Nov 06 '09 at 17:45
source share
10 answers

I would suggest 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 to get the property. Now the only recall is to get the value.

However

Instead, I would recommend using a PropertyDescriptor , as this will allow you to assign a custom TypeDescriptor to your type, which allows you to have easy operations to retrieve properties and values. In the absence of a custom descriptor, it will still return to reflection.

 PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName"); query = query.OrderBy(x => prop.GetValue(x)); 

To speed it up, check out the Marc Gravel HyperDescriptor project in CodeProject. I have used this with great success; It is a life saver for high-performance data bindings and dynamic operations of business object properties.

+96
Nov 06 '09 at 17:53
source share

I'm a little late to the party, but hope this can 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 normal for internal collections, however it will not work when sorting should be done in the source (be it SQL, MongoDb, etc.) Until pagination.

The following code example shows the IQueryable extention methods for OrderBy and OrderByDescending and can be used as follows:

 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.

+48
Feb 21 '14 at 13:53
source share

I liked @Mark Powell's answer, but as @ShuberFu said, it throws an 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 are value types, such as integer ones, since it would not implicitly put an int in an object.

Using the ideas of Christopher Andersson and Mark Gravell, I found a way to construct the Queryable function using the property name and still work with the Entity Framework. I also included the optional IComparer parameter. Note: IComparer does not work with Entity Framework and should be skipped when 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) ) ); } } 
+17
May 19 '17 at 1:56 pm
source share

Yes, I don’t think there is another way than Reflection.

Example:

 query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 
+12
Nov 06 '09 at 17:47
source share
 query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null)); 

Trying to remember the exact syntax from my head, but I think it is correct.

+5
Nov 06 '09 at 17:51
source share

Reflection is the answer!

 typeof(YourType).GetProperty("ProductId").GetValue(theInstance); 

There are many things you can do to cache the reflected PropertyInfo, check for bad lines, write a query comparison function, etc., but you do all of this at the core.

+2
Nov 06 '09 at 17:49
source share

You can use dynamic Linq - check out this blog.

Also check out this StackOverFlow post ...

+2
Nov 06 '09 at 18:29
source share

More productive than reflection enhancement for 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")); 

You may also need to cache lambas (e.g. in the <> dictionary)

+2
Jan 15 '18 at 12:29
source share

Dynamic expressions can also solve this problem. You can use string queries through LINQ expressions that could be dynamically created at runtime.

 var query = query .Where("Category.CategoryName == @0 and Orders.Count >= @1", "Book", 10) .OrderBy("ProductId") .Select("new(ProductName as Name, Price)"); 
+1
Feb 06 '18 at 8:36
source share

Adding a new, improved solution for the current year. Reflection should be your last choice, as we have expression trees that you can use to generate custom code / delegates at runtime. Also note that, according to another answer, getting PropertyInfo and reusing it is not .GetValue() performance, since it is the .GetValue() method .GetValue() bears the cost.

 public class Product { public int ProductId { get; set; } } static Func<T, object> GetPropertyAccessor<T>(string propertyName) { var typeOfT = typeof(T); var pi = typeOfT.GetProperty(propertyName); var p = Expression.Parameter(typeOfT); var memberAccess = Expression.Convert(Expression.MakeMemberAccess(p, pi), typeof(object)); //Put a breakpoint here and look at the DebugView property where you can see that //it generated a call to the ProductId property var lami = Expression.Lambda<Func<T, object>>(memberAccess, p); //This line compiles the above expression into a reusable delegate var deli = lami.Compile(); //Return it for use return deli; } 

Use it like this:

 //Get your custom delegate var deli = GetPropertyAccessor<Product>("ProductId"); //Use it directly in LINQ to entities (or objects) var ordered = products.OrderByDescending(deli).ToArray(); 

The code generated by the expression tree in GetPropertyAccessor is here:

 .Lambda #Lambda1<System.Func'2[Program+Product,System.Object]>(Program+Product $var1) { (System.Object)$var1.ProductId } 

This is basically the equivalent of how you created a specific method to get ProductId :

 static int GetProductId(Product product) { return product.ProductId; } 

You will then pass the link to this method in your request, as you did with the generated delegate:

 var ordered = products.OrderByDescending(GetProductId).ToArray(); 

You can do a lot more with expression trees. I recommend everyone to study them.

0
Jan 17 '19 at 13:37
source share



All Articles