C# - code to order by a property using the property name as a string

C#.NetLinqLinq to-Entities

C# Problem Overview


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.

C# Solutions


Solution 1 - C#

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.

Solution 2 - C#

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.

Solution 3 - C#

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

Solution 4 - C#

Yes, I don't think there's another way than Reflection.

Example:

query = query.OrderBy(x => x.GetType().GetProperty("ProductId").GetValue(x, null));

Solution 5 - C#

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.

Solution 6 - C#

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)

enter image description here

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.

enter image description here

Demo on dotnetfiddle

Solution 7 - C#

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.

Solution 8 - C#

You can use dynamic Linq - check out this blog.

Also check out this StackOverFlow post...

Solution 9 - C#

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

Solution 10 - C#

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

Solution 11 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeremyView Question on Stackoverflow
Solution 1 - C#Adam RobinsonView Answer on Stackoverflow
Solution 2 - C#Mark PowellView Answer on Stackoverflow
Solution 3 - C#David SpechtView Answer on Stackoverflow
Solution 4 - C#Alon GubkinView Answer on Stackoverflow
Solution 5 - C#dkackmanView Answer on Stackoverflow
Solution 6 - C#Nguyễn Văn PhongView Answer on Stackoverflow
Solution 7 - C#Sebastian GoodView Answer on Stackoverflow
Solution 8 - C#Partha ChoudhuryView Answer on Stackoverflow
Solution 9 - C#gdbdableView Answer on Stackoverflow
Solution 10 - C#ali-myousefiView Answer on Stackoverflow
Solution 11 - C#AbolfazlView Answer on Stackoverflow