Reputation: 8183
I am attempting to sort a collection based on a property.
So I do not know the property until runtime that I want to sort on.
The following works on the primary object but not any child objects
var prop = TypeDescriptor.GetProperties(typeof(TvRequests)).Find(sortProperty, tru
if (sortProperty.Contains('.'))
{
// This is a navigation property currently not supported
prop = TypeDescriptor.GetProperties(typeof(TvRequests)).Find("Title", true);
}
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
: allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
So the sortProperty
is passed into my method and is a string and can be something like title
or date
and it works. But if I attempt to access a child property of my TvRequests
object it will not work e.g. requestedUser.username
This is a trimmed down version of my objects that I'm referring to in this question:
public class TvRequests
{
public string Title { get; set; }
[ForeignKey(nameof(RequestedUserId))]
public OmbiUser RequestedUser { get; set; }
}
public class OmbiUser
{
public string Username;
}
My question is how would I be able to access any children properties like the above dynamically?
Upvotes: 1
Views: 601
Reputation: 3634
you can try something like this..
public static class QueryableExtensions
{
public enum SortDirection { ASC,DESC}
static LambdaExpression CreateExpression(Type type, string propertyName)
{
var param = Expression.Parameter(type, "x");
Expression body = param;
body = propertyName.Split('.')
.Select(prop => body = Expression.PropertyOrField(body, prop))
.Last();
return Expression.Lambda(body, param);
}
public static IQueryable<T> SortBy<T>(this IQueryable<T> source,string expressionField,SortDirection sortDirection = SortDirection.ASC)
{
var lambdaExpression = CreateExpression(typeof(T), expressionField) as dynamic;
return sortDirection == SortDirection.ASC ? Queryable.OrderBy(source,lambdaExpression) : Queryable.OrderByDescending(source, lambdaExpression);
}
}
types
public class TvRequests
{
public string Title { get; set; }
public OmbiUser RequestedUser { get; set; }
public DateTime Date { get; set; }
}
public class OmbiUser
{
public string Username;
public DateTime Date { get; set; }
}
using
List<TvRequests> reqList = new List<TvRequests>();
reqList.Add(new TvRequests {
Title = "A",
Date = DateTime.Now.AddDays(-1),
RequestedUser = new OmbiUser
{
Username = "A",
Date = DateTime.Now.AddDays(-1)
}
});
reqList.Add(new TvRequests
{
Title = "C",
Date = DateTime.Now.AddDays(1),
RequestedUser = new OmbiUser
{
Username = "C",
Date = DateTime.Now.AddDays(1)
}
});
reqList.Add(new TvRequests
{
Title = "B",
Date = DateTime.Now,
RequestedUser = new OmbiUser
{
Username = "B",
Date = DateTime.Now
}
});
foreach (var item in reqList.AsQueryable().SortBy("Date", SortDirection.DESC))
Debug.WriteLine(item.Title);
foreach (var item in reqList.AsQueryable().SortBy("RequestedUser.Date"))
Debug.WriteLine(item.Title);
foreach (var item in reqList.AsQueryable().SortBy("RequestedUser.UserName",SortDirection.DESC))
Debug.WriteLine(item.Title);
Upvotes: 1
Reputation: 64259
Use EF.Property.
// Get the string name of the property here
string propertyName = "Title";
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
? allRequests.OrderBy(x => EF.Property<string>(x, propertyName)).ToList()
: allRequests.OrderByDescending(x => EF.Property<string>(x, propertyName)).ToList();
Something along the lines may work (untested, but compiles)
// static for caching & performance
static private MethodInfo efPropertyGenericMethod = typeof(EF).GetTypeInfo().GetDeclaredMethod("Property");
Expression SortBy<TEntity>(Type type, string propertyName)
{
var xParam = Expression.Parameter(typeof(TEntity), "x");
// set T generic type here for EF.Property<T>
var efPropertyMethod = efPropertyGenericMethod.MakeGenericMethod(type);
// Creates a Lambda
Expression lambda = Expression.Lambda(
// Calls a method. First parameter is null for static calls
Expression.Call(null,
efPropertyMethod, // our cosntructed generic Version of EF.Property<T>
xParam, // Pass the x Parameter
Expression.Constant(propertyName, typeof(string)) // the propertyName asconstant
),
xParam
);
return lambda;
};
To be used as
allRequests.OrderBy(SortBy<TvRequests>(propertyType, propertyName))
Please note that that SortBy
isn't called within the lambda. The below would be wrong (there's no x =>
in the line above).
allRequests.OrderBy(x => SortBy<TvRequests>(propertyType, propertyName))
What it does? SortBy generates an expression tree equivalent of x => EF.Property<T>(x, "MyPropertyName")
.
Edit:
Updated the method, so x is also passed to EF.Property(x, propertyName)
Upvotes: 1