I am working on an ASP.NET MVC projet using EF code first, and I am facing a situation where I need to order by an enum description:
public partial class Item
{
public enum MyEnumE
{
[Description("description of enum1")]
Enum1,
[Description("description of enum2")]
Enum2,
...
}
public MyEnumE MyEnum { get; set; }
}
Here is the Search and SortAndPaginate functions:
public async Task<IPagedList<Item>> Search(ItemCriteria criteria, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb)
{
var itemFilter = GenerateFilter(criteria);
var items = entities.Items.Where(itemFilter);
return await SortAndPaginate(items, sortName, sortOrder, pageNb);
}
private async Task<IPagedList<Item>> SortAndPaginate(IQueryable<Item> items, SortableTypeE sortName, SortOrder.TypeE sortOrder, int pageNb)
{
IOrderedQueryable<Item> result = null;
switch (sortName)
{
...
case SortableTypeE.Type:
result = sortOrder == SortOrder.TypeE.ASC
? items.OrderBy(i => i.MyEnum.GetDescription())
: items.OrderByDescending(i => i.MyEnum.GetDescription());
result = result.ThenBy(i => i.SomeOtherProperty);
break;
...
}
if (result != null)
{
return await result.ToPagedListAsync(pageNb, 10);
}
return PagedListHelper.Empty<Item>();
}
The problem is that the Item table can be quite huge.
I thought about calling ToListAsync right after entities.Items.Where(itemFilter) but this will get back all filtered items although I only need one page. Does not sound like a good idea.
But if I don't do that EF won't know about GetDescription() mathod and I can only think about two solutions:
- Change my database column to a string (the enum description) instead of the enum itself (but sounds like a hack to me)
- Or alphabetically order MyEnumE components directly in the enum declaration (seems dirty and quite unmaintainable too)
I'm quite stuck since I'm concerned about performances if I call ToListAsync right after filtering, all other solutions seem dirty, and I absolutely need a IPagedList returned from the Search method.
Would anyone have an idea about how to deal with this issue ?
Thanks a lot.
UPDATE
Here is the GetDescription method (can change it if necessary):
public static string GetDescription(this Enum e)
{
FieldInfo fi = e.GetType().GetField(e.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
return attributes[0].Description;
else
return e.ToString();
}
SOLUTIONS
I'll finally go for Ivan Stoev's suggestion because my project is mainly based on Linq (using Linq instead of stored procedures etc.), so this solution seems more suitable to my particular case than creating reference tables.
However Niyoko Yuliawan's and Michael Freidgeim's are also really good answers to me, anyone reading this post and having a more database approach should go for their solutions ;)
Thanks a lot to all of you.
I would go with dynamic expression. It's more flexible and can easily be changed w/o affecting the database tables and queries.
However, instead of sorting by description strings in the database, I would create ordered map in memory, associating int "order" value with each enum value like this:
public static class EnumHelper
{
public static Expression<Func<TSource, int>> DescriptionOrder<TSource, TEnum>(this Expression<Func<TSource, TEnum>> source)
where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum) throw new InvalidOperationException();
var body = ((TEnum[])Enum.GetValues(enumType))
.OrderBy(value => value.GetDescription())
.Select((value, ordinal) => new { value, ordinal })
.Reverse()
.Aggregate((Expression)null, (next, item) => next == null ? (Expression)
Expression.Constant(item.ordinal) :
Expression.Condition(
Expression.Equal(source.Body, Expression.Constant(item.value)),
Expression.Constant(item.ordinal),
next));
return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
}
public static string GetDescription<TEnum>(this TEnum value)
where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum) throw new InvalidOperationException();
var name = Enum.GetName(enumType, value);
var field = typeof(TEnum).GetField(name, BindingFlags.Static | BindingFlags.Public);
return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
}
}
The usage would be like this:
case SortableTypeE.Type:
var order = EnumHelper.DescriptionOrder((Item x) => x.MyEnum);
result = sortOrder == SortOrder.TypeE.ASC
? items.OrderBy(order)
: items.OrderByDescending(order);
result = result.ThenBy(i => i.SomeOtherProperty);
break;
which would generate expression like this:
x => x.MyEnum == Enum[0] ? 0 :
x.MyEnum == Enum[1] ? 1 :
...
x.MyEnum == Enum[N-2] ? N - 2 :
N - 1;
where 0,1,..N-2 is the corresponding index in the value list sorted by description.
Alternative 1
You can do it by projecting enum into custom value and sort by it.
Example:
items
.Select(x=> new
{
x,
Desc = (
x.Enum == Enum.One ? "Desc One"
: x.Enum == Enum.Two ? "Desc Two"
... and so on)
})
.OrderBy(x=>x.Desc)
.Select(x=>x.x);
Entity framework then will generate SQL something like this
SELECT
*
FROM
YourTable
ORDER BY
CASE WHEN Enum = 1 THEN 'Desc One'
WHEN Enum = 2 THEN 'Desc Two'
...and so on
END
If you have a lot of query like this, you can create extension method
public static IQueryable<Entity> OrderByDesc(this IQueryable<Entity> source)
{
return source.Select(x=> new
{
x,
Desc = (
x.Enum == Enum.One ? "Desc One"
: x.Enum == Enum.Two ? "Desc Two"
... and so on)
})
.OrderBy(x=>x.Desc)
.Select(x=>x.x);
}
And call it when you need it
var orderedItems = items.OrderByDesc();
Alternative 2
Another alternative solution is to create additional table that map enum value to enum description and join your table to this table. This solution will be more performant because you can create index on enum description column.
Alternative 3
If you want dynamic expression based on your enum description attribute, you can build yourself
Helper Class
public class Helper
{
public MyEntity Entity { get; set; }
public string Description { get; set; }
}
Get dynamically built expression
public static string GetDesc(MyEnum e)
{
var type = typeof(MyEnum);
var memInfo = type.GetMember(e.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute),
false);
return ((DescriptionAttribute)attributes[0]).Description;
}
private static Expression<Func<MyEntity, Helper>> GetExpr()
{
var descMap = Enum.GetValues(typeof(MyEnum))
.Cast<MyEnum>()
.ToDictionary(value => value, GetDesc);
var paramExpr = Expression.Parameter(typeof(MyEntity), "x");
var expr = (Expression) Expression.Constant(string.Empty);
foreach (var desc in descMap)
{
// Change string "Enum" below with your enum property name in entity
var prop = Expression.Property(paramExpr, typeof(MyEntity).GetProperty("Enum"));
expr = Expression.Condition(Expression.Equal(prop, Expression.Constant(desc.Key)),
Expression.Constant(desc.Value), expr);
}
var newExpr = Expression.New(typeof(Helper));
var bindings = new MemberBinding[]
{
Expression.Bind(typeof(Helper).GetProperty("Entity"), paramExpr),
Expression.Bind(typeof(Helper).GetProperty("Description"), expr)
};
var body = Expression.MemberInit(newExpr, bindings);
return (Expression<Func<MyEntity, Helper>>) Expression.Lambda(body, paramExpr);
}
Call it like this
var e = GetExpr();
items.Select(e)
.OrderBy(x => x.Description)
.Select(x => x.Entity);
Change my database column to a string (the enum description) instead
of the enum itself (but sounds like a hack to me).
Opposite, for data-driven application it's better to describe Item property in the database reference table MyItemProperty(MyPropKey,MyPropDescription) and have MyPropKey column in your Items table.
It has a few benefits, e.g.
allow to add new property values without need to change code;
allow to write SQL reports having all information in the database without writing c#;
performance optimisation can be done on SQL
level just by requesting one page;
no enum - less code to maintain.
To keep it simple and with a good performance, I would order the enum manually, you only have to do it once, and it will help a lot
public enum MyEnumE
{
Enum1 = 3,
Enum2 = 1,
Enum3 = 2, // set the order here...
}
Here's a simplified example using a join:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace ConsoleApplication
{
public partial class Item
{
public enum MyEnumE
{
[Description("description of enum1")]
Enum1,
[Description("description of enum2")]
Enum2
}
public Item(MyEnumE myEnum)
{
MyEnum = myEnum;
}
public MyEnumE MyEnum { get; set; }
}
class Program
{
private static IEnumerable<KeyValuePair<int, int>> GetEnumRanks(Type enumType)
{
var values = Enum.GetValues(enumType);
var results = new List<KeyValuePair<int, string>>(values.Length);
foreach (int value in values)
{
FieldInfo fieldInfo = enumType.GetField(Enum.GetName(enumType, value));
var attribute = (DescriptionAttribute)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute));
results.Add(new KeyValuePair<int, string>(value, attribute.Description));
}
return results.OrderBy(x => x.Value).Select((x, i) => new KeyValuePair<int, int>(x.Key, i));
}
static void Main(string[] args)
{
var itemsList = new List<Item>();
itemsList.Add(new Item(Item.MyEnumE.Enum1));
itemsList.Add(new Item(Item.MyEnumE.Enum2));
itemsList.Add(new Item(Item.MyEnumE.Enum2));
itemsList.Add(new Item(Item.MyEnumE.Enum1));
IQueryable<Item> items = itemsList.AsQueryable();
var descriptions = GetEnumRanks(typeof(Item.MyEnumE));
//foreach (var i in descriptions)
// Console.WriteLine(i.Value);
var results = items.Join(descriptions, a => (int)a.MyEnum, b => b.Key, (x, y) => new { Item = x, Rank = y.Value }).OrderBy(x => x.Rank).Select(x => x.Item);
foreach (var i in results)
Console.WriteLine(i.MyEnum.ToString());
Console.WriteLine("\nPress any key...");
Console.ReadKey();
}
}
}
I had a similar problem to solve, only that my ordering had to be dynamic, that is the sort by column parameter is a string.
The boolean sorting also had to be customized in that sense that true comes before false (e.g. 'Active' is before 'Inactive').
I'm sharing here the complete code with you, so you can spare your time. In case you find spots for improvement, please feel free to share in a comment.
private static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> query, SortField sortField)
{
var queryParameterExpression = Expression.Parameter(typeof(T), "x");
var orderByPropertyExpression = GetPropertyExpression(sortField.FieldName, queryParameterExpression);
Type orderByPropertyType = orderByPropertyExpression.Type;
LambdaExpression lambdaExpression = Expression.Lambda(orderByPropertyExpression, queryParameterExpression);
if (orderByPropertyType.IsEnum)
{
orderByPropertyType = typeof(int);
lambdaExpression = GetExpressionForEnumOrdering<T>(lambdaExpression);
}
else if (orderByPropertyType == typeof(bool))
{
orderByPropertyType = typeof(string);
lambdaExpression =
GetExpressionForBoolOrdering(orderByPropertyExpression, queryParameterExpression);
}
var orderByExpression = Expression.Call(
typeof(Queryable),
sortField.SortDirection == SortDirection.Asc ? "OrderBy" : "OrderByDescending",
new Type[] { typeof(T), orderByPropertyType },
query.Expression,
Expression.Quote(lambdaExpression));
return query.Provider.CreateQuery<T>(orderByExpression);
}
The shared GetPropertyExpression has been simplified a bit, to exclude the nested property handling.
private static MemberExpression GetPropertyExpression(string propertyName, ParameterExpression queryParameterExpression)
{
MemberExpression result = Expression.Property(queryParameterExpression, propertyName);
return result;
}
Here is the slightly modified code (from the accepted solution) to handle the Enum ordering.
private static Expression<Func<TSource, int>> GetExpressionForEnumOrdering<TSource>(LambdaExpression source)
{
var enumType = source.Body.Type;
if (!enumType.IsEnum)
throw new InvalidOperationException();
var body = ((int[])Enum.GetValues(enumType))
.OrderBy(value => GetEnumDescription(value, enumType))
.Select((value, ordinal) => new { value, ordinal })
.Reverse()
.Aggregate((Expression)null, (next, item) => next == null ? (Expression)
Expression.Constant(item.ordinal) :
Expression.Condition(
Expression.Equal(source.Body, Expression.Convert(Expression.Constant(item.value), enumType)),
Expression.Constant(item.ordinal),
next));
return Expression.Lambda<Func<TSource, int>>(body, source.Parameters[0]);
}
And the boolean ordering as well.
private static LambdaExpression GetExpressionForBoolOrdering(MemberExpression orderByPropertyExpression, ParameterExpression queryParameterExpression)
{
var firstWhenActiveExpression = Expression.Condition(orderByPropertyExpression,
Expression.Constant("A"),
Expression.Constant("Z"));
return Expression.Lambda(firstWhenActiveExpression, new[] { queryParameterExpression });
}
Also the GetEnumDescription has been modified to receive the Type as the parameter, so it can be called without a generic.
private static string GetEnumDescription(int value, Type enumType)
{
if (!enumType.IsEnum)
throw new InvalidOperationException();
var name = Enum.GetName(enumType, value);
var field = enumType.GetField(name, BindingFlags.Static | BindingFlags.Public);
return field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? name;
}
The SortField is a simple abstraction containing the string column property to be sorted upon and the direction of the sort. For the sake of simplicity I am also not sharing that one here.
Cheers!
Related
We are using Expressions to build a refined select statement (being used by GraphQL .Net) that only queries the properties asked for.
We parse the raw query and use a recursive method to build an expression that we use to generate our select (via entity framework)
Our code is HEAVILY based on this answer provided by #Svyatoslav Danyliv.
Our expressionbuilder class looks like this:
public class ExpressionBuilder
{
public Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(string members)
{
return BuildSelector<TSource, TTarget>(members.Split(',').Select(m => m.Trim()));
}
public Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>(IEnumerable<string> members)
{
var parameter = Expression.Parameter(typeof(TSource), "e");
var body = NewObject(typeof(TTarget), parameter, members.Select(m => m.Split('.')));
return Expression.Lambda<Func<TSource, TTarget>>(body, parameter);
}
private Expression NewObject(Type targetType, Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
{
var bindings = new List<MemberBinding>();
var target = Expression.Constant(null, targetType);
foreach (var memberGroup in memberPaths.GroupBy(path => path[depth]))
{
var memberName = memberGroup.Key;
var targetMember = Expression.PropertyOrField(target, memberName);
var sourceMember = Expression.PropertyOrField(source, memberName);
var childMembers = memberGroup.Where(path => depth + 1 < path.Length).ToList();
Expression targetValue = null;
if (!childMembers.Any())
{
targetValue = sourceMember;
}
else
{
if (IsEnumerableType(targetMember.Type, out var sourceElementType) &&
IsEnumerableType(targetMember.Type, out var targetElementType))
{
var sourceElementParam = Expression.Parameter(sourceElementType, "e");
targetValue = NewObject(targetElementType, sourceElementParam, childMembers, depth + 1);
targetValue = Expression.Call(typeof(Enumerable), nameof(Enumerable.Select),
new[] { sourceElementType, targetElementType }, sourceMember,
Expression.Lambda(targetValue, sourceElementParam));
targetValue = CorrectEnumerableResult(targetValue, targetElementType, targetMember.Type);
}
else
{
targetValue = NewObject(targetMember.Type, sourceMember, childMembers, depth + 1);
}
}
bindings.Add(Expression.Bind(targetMember.Member, targetValue));
}
return Expression.MemberInit(Expression.New(targetType), bindings);
}
private bool IsEnumerableType(Type type, out Type elementType)
{
foreach (var intf in type.GetInterfaces())
{
if (intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
elementType = intf.GetGenericArguments()[0];
return true;
}
}
elementType = null;
return false;
}
private bool IsSameCollectionType(Type type, Type genericType, Type elementType)
{
var result = genericType.MakeGenericType(elementType).IsAssignableFrom(type);
return result;
}
private Expression CorrectEnumerableResult(Expression enumerable, Type elementType, Type memberType)
{
if (memberType == enumerable.Type)
return enumerable;
if (memberType.IsArray)
return Expression.Call(typeof(Enumerable), nameof(Enumerable.ToArray), new[] { elementType }, enumerable);
if (IsSameCollectionType(memberType, typeof(List<>), elementType)
|| IsSameCollectionType(memberType, typeof(ICollection<>), elementType)
|| IsSameCollectionType(memberType, typeof(IReadOnlyList<>), elementType)
|| IsSameCollectionType(memberType, typeof(IReadOnlyCollection<>), elementType))
return Expression.Call(typeof(Enumerable), nameof(Enumerable.ToList), new[] { elementType }, enumerable);
throw new NotImplementedException($"Not implemented transformation for type '{memberType.Name}'");
}
}
and the usage is as follows:
var builder = new ExpressionBuilder();
var statement = builder.BuildSelector<Payslip, Payslip>(selectStatement);
The select statement above looks like "id,enrollment.id" where we are selecting parentobject.id and parentobject.enrollment.id
This statement variable is then used in a EntityQueryable.Select(statement) against a filtered database collection there we have essentially done the 'where' aspect of the query.
When working correctly the sql generated is ONLY the fields requested, and it works great. However in this instance it is trying to access the .Id property of a null value and constantly fails.
It is currently failing because the "Enrollment" property of the parentObject is not set, thus null.
We get the following error
System.InvalidOperationException: Nullable object must have a value.
at System.Nullable`1.get_Value()
at lambda_method2198(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
I've tried to implement a try catch expression but keep failing, and end up with the exact same error. At this time, we aren't hitting the database, so we don't know if the value will be null or not (it works fine if it's not null).
I am by no means an expert on expressions, and am really struggling to find just where I should be wrapping this value, or even how. Ideally I'd just like to return null, but worst case a default enrollment object could be detected client side and possibly handled ok.
** UPDATE!! **
Reading into this I think that this is a result of this issue. The workaround would involve building something like this null check into the expression builder.
.Select(r => new Something()
{
X1 = r.X1,
X2 = r.X2,
X = new X()
{
Name= r.X.Name
},
XX = r.XXNullableId == null ? null : new XX()
{
XNumber = r.XX.XNumber
}
})
I have created a VERY simple EF Core Application here that creates a simple three table database and recreates the problem.
I believe the workaround mentioned above will work, it's just a matter of figuring out how to add it to the expression builder.
So I figured this one out (sort of) by special casing the property that causes the issue.
Due to the nature of our model, this situation will only occur in a few places, it's easier to add some workarounds for them, that have the expression builder do some strange null check on every property.
I created a simple attribute that I put on the 'Object' Property and have it point to the property I need to check for null.
That looks like this:
[System.AttributeUsage(System.AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
public class ExpressionBuilderNullCheckForPropertyAttribute : Attribute
{
public ExpressionBuilderNullCheckForPropertyAttribute(string propertyName)
{
this.PropertyName = propertyName;
}
public string PropertyName { get; set; }
}
So now, my 'Person' Class (from the linked example repo above) now looks like this.
public class Person
{
[Required]
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Nullable<Guid> VetId { get; set; }
[ExpressionBuilderNullCheckForProperty(nameof(VetId))]
public Vet Vet { get; set; }
public List<Pet> Pets { get; set; }
}
Then I added a check into the ExpressionBuilder class to check for that attribute, if it is present, lookup the property it has mentioned, then perform a null check. If that property is null, then return a default object for that type. Which, in this case, is null.
if (targetMember.Member.GetCustomAttributesData().Any(x => x.AttributeType == typeof(ExpressionBuilderNullCheckForPropertyAttribute)))
{
var att = (ExpressionBuilderNullCheckForPropertyAttribute)targetMember.Member.GetCustomAttributes(true).First(x => x.GetType() == typeof(ExpressionBuilderNullCheckForPropertyAttribute));
var propertyToCheck = att.PropertyName;
var valueToCheck = Expression.PropertyOrField(source, propertyToCheck);
var nullCheck = Expression.Equal(valueToCheck, Expression.Constant(null, targetMember.Type));
var checkForNull = Expression.Condition(nullCheck, Expression.Default(targetMember.Type), targetValue);
bindings.Add(Expression.Bind(targetMember.Member, checkForNull));
}
else
{
bindings.Add(Expression.Bind(targetMember.Member, targetValue));
}
I have updated the example repo if anyone wants to see the fix working.
I have the name of the "sort by property" in a string. I will need to use Lambda/Linq to sort the list of objects.
Ex:
public class Employee
{
public string FirstName {set; get;}
public string LastName {set; get;}
public DateTime DOB {set; get;}
}
public void Sort(ref List<Employee> list, string sortBy, string sortDirection)
{
//Example data:
//sortBy = "FirstName"
//sortDirection = "ASC" or "DESC"
if (sortBy == "FirstName")
{
list = list.OrderBy(x => x.FirstName).toList();
}
}
Instead of using a bunch of ifs to check the fieldname (sortBy), is there a cleaner way of doing the sorting
Is sort aware of datatype?
This can be done as
list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );
The .NET framework is casting the lambda (emp1,emp2)=>int as a Comparer<Employee>.
This has the advantage of being strongly typed.
If you need the descending/reverse order invert the parameters.
list.Sort( (emp1,emp2)=>emp2.FirstName.CompareTo(emp1.FirstName) );
One thing you could do is change Sort so it makes better use of lambdas.
public enum SortDirection { Ascending, Descending }
public void Sort<TKey>(ref List<Employee> list,
Func<Employee, TKey> sorter, SortDirection direction)
{
if (direction == SortDirection.Ascending)
list = list.OrderBy(sorter);
else
list = list.OrderByDescending(sorter);
}
Now you can specify the field to sort when calling the Sort method.
Sort(ref employees, e => e.DOB, SortDirection.Descending);
You could use Reflection to get the value of the property.
list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) )
.ToList();
Where TypeHelper has a static method like:
public static class TypeHelper
{
public static object GetPropertyValue( object obj, string name )
{
return obj == null ? null : obj.GetType()
.GetProperty( name )
.GetValue( obj, null );
}
}
You might also want to look at Dynamic LINQ from the VS2008 Samples library. You could use the IEnumerable extension to cast the List as an IQueryable and then use the Dynamic link OrderBy extension.
list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
This is how I solved my problem:
List<User> list = GetAllUsers(); //Private Method
if (!sortAscending)
{
list = list
.OrderBy(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
.ToList();
}
else
{
list = list
.OrderByDescending(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
.ToList();
}
Building the order by expression can be read here
Shamelessly stolen from the page in link:
// First we define the parameter that we are going to use
// in our OrderBy clause. This is the same as "(person =>"
// in the example above.
var param = Expression.Parameter(typeof(Person), "person");
// Now we'll make our lambda function that returns the
// "DateOfBirth" property by it's name.
var mySortExpression = Expression.Lambda<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);
// Now I can sort my people list.
Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();
You could use reflection to access the property.
public List<Employee> Sort(List<Employee> list, String sortBy, String sortDirection)
{
PropertyInfo property = list.GetType().GetGenericArguments()[0].
GetType().GetProperty(sortBy);
if (sortDirection == "ASC")
{
return list.OrderBy(e => property.GetValue(e, null));
}
if (sortDirection == "DESC")
{
return list.OrderByDescending(e => property.GetValue(e, null));
}
else
{
throw new ArgumentOutOfRangeException();
}
}
Notes
Why do you pass the list by reference?
You should use a enum for the sort direction.
You could get a much cleaner solution if you would pass a lambda expression
specifying the property to sort by instead of the property name as a string.
In my example list == null will cause a NullReferenceException, you should catch this case.
Sort uses the IComparable interface, if the type implements it.
And you can avoid the ifs by implementing a custom IComparer:
class EmpComp : IComparer<Employee>
{
string fieldName;
public EmpComp(string fieldName)
{
this.fieldName = fieldName;
}
public int Compare(Employee x, Employee y)
{
// compare x.fieldName and y.fieldName
}
}
and then
list.Sort(new EmpComp(sortBy));
Answer for 1.:
You should be able to manually build an expression tree that can be passed into OrderBy using the name as a string. Or you could use reflection as suggested in another answer, which might be less work.
Edit: Here is a working example of building an expression tree manually. (Sorting on X.Value, when only knowing the name "Value" of the property). You could (should) build a generic method for doing it.
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
private static readonly Random rand = new Random();
static void Main(string[] args)
{
var randX = from n in Enumerable.Range(0, 100)
select new X { Value = rand.Next(1000) };
ParameterExpression pe = Expression.Parameter(typeof(X), "value");
var expression = Expression.Property(pe, "Value");
var exp = Expression.Lambda<Func<X, int>>(expression, pe).Compile();
foreach (var n in randX.OrderBy(exp))
Console.WriteLine(n.Value);
}
public class X
{
public int Value { get; set; }
}
}
Building an expression tree requires you to know the particpating types, however. That might or might not be a problem in your usage scenario. If you don't know what type you should be sorting on, it will propably be easier using reflection.
Answer for 2.:
Yes, since Comparer<T>.Default will be used for the comparison, if you do not explicitly define the comparer.
The solution provided by Rashack does not work for value types (int, enums, etc.) unfortunately.
For it to work with any type of property, this is the solution I found:
public static Expression<Func<T, object>> GetLambdaExpressionFor<T>(this string sortColumn)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(type, "x");
var body = Expression.PropertyOrField(parameterExpression, sortColumn);
var convertedBody = Expression.MakeUnary(ExpressionType.Convert, body, typeof(object));
var expression = Expression.Lambda<Func<T, object>>(convertedBody, new[] { parameterExpression });
return expression;
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
public static class EnumerableHelper
{
static MethodInfo orderBy = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName)
{
var pi = typeof(TSource).GetProperty(propertyName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
var sourceParam = Expression.Parameter(typeof(IEnumerable<TSource>), "source");
return
Expression.Lambda<Func<IEnumerable<TSource>, IOrderedEnumerable<TSource>>>
(
Expression.Call
(
orderBy.MakeGenericMethod(typeof(TSource), pi.PropertyType),
sourceParam,
Expression.Lambda
(
typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType),
Expression.Property(selectorParam, pi),
selectorParam
)
),
sourceParam
)
.Compile()(source);
}
public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName, bool ascending)
{
return ascending ? source.OrderBy(propertyName) : source.OrderBy(propertyName).Reverse();
}
}
Another one, this time for any IQueryable:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class IQueryableHelper
{
static MethodInfo orderBy = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
static MethodInfo orderByDescending = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderByDescending" && x.GetParameters().Length == 2).First();
public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, params string[] sortDescriptors)
{
return sortDescriptors.Length > 0 ? source.OrderBy(sortDescriptors, 0) : source;
}
static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string[] sortDescriptors, int index)
{
if (index < sortDescriptors.Length - 1) source = source.OrderBy(sortDescriptors, index + 1);
string[] splitted = sortDescriptors[index].Split(' ');
var pi = typeof(TSource).GetProperty(splitted[0], BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.IgnoreCase);
var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
return source.Provider.CreateQuery<TSource>(Expression.Call((splitted.Length > 1 && string.Compare(splitted[1], "desc", StringComparison.Ordinal) == 0 ? orderByDescending : orderBy).MakeGenericMethod(typeof(TSource), pi.PropertyType), source.Expression, Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType), Expression.Property(selectorParam, pi), selectorParam)));
}
}
You can pass multiple sort criteria, like this:
var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });
Adding to what #Samuel and #bluish did. This is much shorter as the Enum was unnecessary in this case. Plus as an added bonus when the Ascending is the desired result, you can pass only 2 parameters instead of 3 since true is the default answer to the third parameter.
public void Sort<TKey>(ref List<Person> list, Func<Person, TKey> sorter, bool isAscending = true)
{
list = isAscending ? list.OrderBy(sorter) : list.OrderByDescending(sorter);
}
If you get sort column name and sort direction as string and don't want to use switch or if\else syntax to determine column, then this example may be interesting for you:
private readonly Dictionary<string, Expression<Func<IuInternetUsers, object>>> _sortColumns =
new Dictionary<string, Expression<Func<IuInternetUsers, object>>>()
{
{ nameof(ContactSearchItem.Id), c => c.Id },
{ nameof(ContactSearchItem.FirstName), c => c.FirstName },
{ nameof(ContactSearchItem.LastName), c => c.LastName },
{ nameof(ContactSearchItem.Organization), c => c.Company.Company },
{ nameof(ContactSearchItem.CustomerCode), c => c.Company.Code },
{ nameof(ContactSearchItem.Country), c => c.CountryNavigation.Code },
{ nameof(ContactSearchItem.City), c => c.City },
{ nameof(ContactSearchItem.ModifiedDate), c => c.ModifiedDate },
};
private IQueryable<IuInternetUsers> SetUpSort(IQueryable<IuInternetUsers> contacts, string sort, string sortDir)
{
if (string.IsNullOrEmpty(sort))
{
sort = nameof(ContactSearchItem.Id);
}
_sortColumns.TryGetValue(sort, out var sortColumn);
if (sortColumn == null)
{
sortColumn = c => c.Id;
}
if (string.IsNullOrEmpty(sortDir) || sortDir == SortDirections.AscendingSort)
{
contacts = contacts.OrderBy(sortColumn);
}
else
{
contacts = contacts.OrderByDescending(sortColumn);
}
return contacts;
}
Solution based on using Dictionary that connects needed for sort column via Expression> and its key string.
I have a class list of records, so user can select to group rows dynamically by property name. For example MenuText, RoleName or ActionName. Then I have to execute grouping so I need a generic method to handle grouping by passing column name.
Example :
public class Menu
{
public string MenuText {get;set;}
public string RoleName {get;set;}
public string ActionName {get;set;}
}
public class Menus
{
var list = new List<Menu>();
list.Add( new Menu {MenuText="abc",RoleName ="Admin", ActionName="xyz"};
list.Add( new Menu {MenuText="abc",RoleName ="Admin", ActionName="xyz"};
list.Add( new Menu {MenuText="abc1",RoleName ="Admin1", ActionName="xyz1"};
list.Add( new Menu {MenuText="abc1",RoleName ="Admin1", ActionName="xyz1"};
/// columnName :- Name of the Menu class ( MenuText or RoleName or ActionName)
public IEnumerable<IGrouping<string,IEnumerable<Menu>>> GroupMenu(string columnName)
{
// Here I want to get group the list of menu by the columnName
}
}
If you're not working with a database you can use Reflection:
private static object GetPropertyValue(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}
Used as:
var grouped = enumeration.GroupBy(x => GetPropertyValue(x, columnName));
This is a pretty raw solution, a better way should be to use Dynamic LINQ:
var grouped = enumeration.GroupBy(columnName, selector);
EDIT Dynamic LINQ maybe needs some explanations. It's not a technology, a library or a brand new framework. It's just a convenient name for a couple (2000 LOC) of helpers methods that let you write such queries. Just download their source (if you don't have VS samples installed) and use them in your code.
The following approach would work with LINQ to Objects as well as with LINQ to EF / NHibernate / etc.
It creates an expression that corresponds to the column / property passed as the string and passes that expression to GroupBy:
private static Expression<Func<Menu, string>> GetGroupKey(string property)
{
var parameter = Expression.Parameter(typeof(Menu));
var body = Expression.Property(parameter, property);
return Expression.Lambda<Func<Menu, string>>(body, parameter);
}
Usage with IQueryable<T> based data sources:
context.Menus.GroupBy(GetGroupKey(columnName));
Usage with IEnumerable<T> based data sources:
list.GroupBy(GetGroupKey(columnName).Compile());
BTW: The return type of your method should be IEnumerable<IGrouping<string, Menu>>, because an IGrouping<string, Menu> already means that there can be multiple Menu instances per key.
Simplest way:
if(columnName == "MextText")
{
return list.GroupBy(x => x.MenuText);
}
if(columnName == "RoleName")
{
return list.GroupBy(x => x.RoleName);
}
if(columnName == "ActionName")
{
return list.GroupBy(x => x.ActionName);
}
return list.GroupBy(x => x.MenuText);
You could also use expression trees.
private static Expression<Func<Menu, string>> GetColumnName(string property)
{
var menu = Expression.Parameter(typeof(Menu), "menu");
var menuProperty = Expression.PropertyOrField(menu, property);
var lambda = Expression.Lambda<Func<Menu, string>>(menuProperty, menu);
return lambda;
}
return list.GroupBy(GetColumnName(columnName).Compile());
This will produce a lambda menu => menu.<PropertyName>.
But there's not really much of a difference until the class gets bloated.
I have done this with Dynamic Linq as suggested by Adriano
public static IEnumerable<IGrouping<object, TElement>> GroupByMany<TElement>(
this IEnumerable<TElement> elements, params string[] groupSelectors)
{
var selectors = new List<Func<TElement, object>>(groupSelectors.Length);
selectors.AddRange(groupSelectors.Select(selector => DynamicExpression.ParseLambda(typeof (TElement), typeof (object), selector)).Select(l => (Func<TElement, object>) l.Compile()));
return elements.GroupByMany(selectors.ToArray());
}
public static IEnumerable<IGrouping<object, TElement>> GroupByMany<TElement>(
this IEnumerable<TElement> elements, params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
Func<TElement, object> selector = groupSelectors.First();
return elements.GroupBy(selector);
}
return null;
}
You solution is simple to implement for any model, I have just made it generic one.
public static Expression<Func<TElement, string>> GetColumnName<TElement>(string property)
{
var menu = Expression.Parameter(typeof(TElement), "groupCol");
var menuProperty = Expression.PropertyOrField(menu, property);
var lambda = Expression.Lambda<Func<TElement, string>>(menuProperty, menu);
return lambda;
}
so called this as below
_unitOfWork.MenuRepository.Get().GroupBy(LinqExtensions.GetColumnName<Menu>("MenuText").Compile());
Thank you very much for the help.
Is there a way I can make the following db query builder generic?
private IQueryable<Foo> ByName(IQueryable<Foo> dbQuery, Query query)
{
string[] searchTerms = query.Data.Replace(" ","").ToLower().Split(',');
if (query.Exclude)
{
return dbQuery.Where(x => searchTerms.All(
y => y != x.Name.Replace(" ", "").ToLower()));
}
return dbQuery.Where(x => searchTerms.Any(
y => y == x.Name.Replace(" ", "").ToLower()));
}
I've got the same function for lots of different properties of Foo. ByCounty, ByTown, ByStreet etc etc.
I've written some functions that return linq before like the following
public Expression<Func<Foo, bool>> FoosAreWithinDistanceFromGeocode(
double distance, Geocode geocode)
{
double distanceSquare = distance * distance;
return foo => ( SqlFunctions.Square((double)(
foo.Address.Geocode.Easting - geocode.Easting)) +
SqlFunctions.Square((double)(fooAddress.Geocode.Northing -
geocode.Northing)) ) <= distanceSquare;
}
But I can't seem to find if the Linq-to-SQL stuff can't use generics or if it's possible to pass properties as generics and that kind of thing.
EDIT: I have this working generically for a single search term.
Where [query.Data == "Foo1"]
return dbQuery.Where(SearchMatch("Name", query.Data));
public Expression<Func<Foo, bool>> SearchMatch(string propertyName, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
var prop = Expression.Property(foo, propertyName);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Anyone have any ideas how to make it work for an array of strings?
You need to define an interface that exposes the properties that you want to access, like so:
public interface IHaveName
{
string Name { get; }
}
Then, on your classes, you would implement the interface:
public class Foo : IHaveName
If you're using the classes generated from a DBML file, these classes are marked with the partial keyword so implementing the interface is as simple as creating a new file, and inserting:
public partial class Foo : IHaveName
Since the property is already declared as public in the other .cs file generated from the .dbml file, the interface is implemented implicitly.
Finally, you would rewrite your ByName method to take a generic type parameter with a constraint that it implement your interface IHaveName:
private IQueryable<T> ByName<T>(IQueryable<T> dbQuery, Query query)
where T : IHaveName
{
// Everything else is the same.
For your other properties (and methods which use them), you could aggregate them together into one interface, or separate them out, depending on your needs.
Based on your edit, if you want to create an expression dynamically, you don't have to give up compile-time safety:
public Expression<Func<Foo, bool>> SearchMatch(
Expression<Func<Foo, string>> property, string searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
Which you then call like so:
var expression = SearchMatch(f => f.Name, "searchTerm");
This ensures that the properties that you are passing to SearchMatch actually exist on Foo. Note if you wanted to make this generic for other scalar property types, you would do the following:
public Expression<Func<Foo, bool>> SearchMatch<T>(
Expression<Func<Foo, T>> property, T searchTerm)
{
var foo = Expression.Parameter(typeof(Foo), "foo");
// Get the property info from the property expression.
var prop = Expression.Property(foo,
(property.Body as MemberExpression).Member as PropertyInfo);
var search = Expression.Constant(searchTerm);
var equal = Expression.Equal(prop, search);
return Expression.Lambda<Func<Foo, bool>>(equal, foo);
}
If I understood what you are trying to achieve reflection might help you. At least if you play nice. Here's a simplified but working example
internal class Program
{
private class Data
{
public string Name { get; set; }
public string Address { get; set; }
public override string ToString()
{
return String.Format("My name is {0} and I'm living at {1}", Name, Address);
}
}
static Expression<Func<Data,bool>> BuildExpression(PropertyInfo prop, IQueryable<string> restrict)
{
return (data) => !restrict.Any(elem => elem == prop.GetValue(data, null));
}
static IQueryable<Data> FilterData(IQueryable<Data> input, Expression<Func<Data, bool>> filter)
{
return input.Where(filter);
}
public static void Main (string[] args)
{
List<Data> list = new List<Data>()
{
new Data {Name = "John", Address = "1st Street"},
new Data {Name = "Mary",Address = "2nd Street"},
new Data {Name = "Carl", Address = "3rd Street"}
};
var filterByNameExpression = BuildExpression(typeof (Data).GetProperty("Name"),
(new List<string> {"John", "Carl"}).AsQueryable());
var filterByAddressExpression = BuildExpression(typeof(Data).GetProperty("Address"),
(new List<string> { "2nd Street"}).AsQueryable());
IQueryable<Data> filetedByName = FilterData(list.AsQueryable(), filterByNameExpression);
IQueryable<Data> filetedByAddress = FilterData(list.AsQueryable(), filterByAddressExpression);
Console.WriteLine("Filtered by name");
foreach (var d in filetedByName)
{
Console.WriteLine(d);
}
Console.WriteLine("Filtered by address");
foreach (var d in filetedByAddress)
{
Console.WriteLine(d);
}
Console.ReadLine();
}
Hovewer, I'\m almost sure it won't work with LINQ-to-SQL. One way to workaround it is to materialize the IQueryable before passing it to such filtering methods (e.g. by calling ToList on them).
I found an example in the VS2008 Examples for Dynamic LINQ that allows you to use a SQL-like string (e.g. OrderBy("Name, Age DESC")) for ordering. Unfortunately, the method included only works on IQueryable<T>. Is there any way to get this functionality on IEnumerable<T>?
Just stumbled into this oldie...
To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.
To get it working with IEnumerable<T> you could add some wrapper methods that go via AsQueryable - but the code below is the core Expression logic needed.
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(
IQueryable<T> source,
string property,
string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach(string prop in props) {
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] {source, lambda});
return (IOrderedQueryable<T>)result;
}
Edit: it gets more fun if you want to mix that with dynamic - although note that dynamic only applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent dynamic queries - MemberExpression doesn't support it). But here's a way to do it with LINQ-to-Objects. Note that the choice of Hashtable is due to favorable locking semantics:
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
private static class AccessorCache
{
private static readonly Hashtable accessors = new Hashtable();
private static readonly Hashtable callSites = new Hashtable();
private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
string name)
{
var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
if(callSite == null)
{
callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
.Create(Binder.GetMember(
CSharpBinderFlags.None,
name,
typeof(AccessorCache),
new CSharpArgumentInfo[] {
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None,
null)
}));
}
return callSite;
}
internal static Func<dynamic,object> GetAccessor(string name)
{
Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
lock (accessors )
{
accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
if(name.IndexOf('.') >= 0) {
string[] props = name.Split('.');
CallSite<Func<CallSite, object, object>>[] arr
= Array.ConvertAll(props, GetCallSiteLocked);
accessor = target =>
{
object val = (object)target;
for (int i = 0; i < arr.Length; i++)
{
var cs = arr[i];
val = cs.Target(cs, val);
}
return val;
};
} else {
var callSite = GetCallSiteLocked(name);
accessor = target =>
{
return callSite.Target(callSite, (object)target);
};
}
accessors[name] = accessor;
}
}
}
return accessor;
}
}
public static IOrderedEnumerable<dynamic> OrderBy(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
static void Main()
{
dynamic a = new ExpandoObject(),
b = new ExpandoObject(),
c = new ExpandoObject();
a.X = "abc";
b.X = "ghi";
c.X = "def";
dynamic[] data = new[] {
new { Y = a },
new { Y = b },
new { Y = c }
};
var ordered = data.OrderByDescending("Y.X").ToArray();
foreach (var obj in ordered)
{
Console.WriteLine(obj.Y.X);
}
}
}
Too easy without any complication:
Add using System.Linq.Dynamic; at the top.
Use vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Edit: to save some time, the System.Linq.Dynamic.Core (System.Linq.Dynamic is deprecated) assembly is not part of the framework, but can be installed from nuget: System.Linq.Dynamic.Core
Just stumbled across this question.
Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
I guess it would work to use reflection to get whatever property you want to sort on:
IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
where some criteria
orderby GetPropertyValue(enumerable,"SomeProperty")
select enumerable
private static object GetPropertyValue(object obj, string property)
{
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}
Note that using reflection is considerably slower than accessing the property directly, so the performance would have to be investigated.
Just building on what others have said. I found that the following works quite well.
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
if (string.IsNullOrEmpty(queryString))
return input;
int i = 0;
foreach (string propname in queryString.Split(','))
{
var subContent = propname.Split('|');
if (Convert.ToInt32(subContent[1].Trim()) == 0)
{
if (i == 0)
input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
}
else
{
if (i == 0)
input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
}
i++;
}
return input;
}
I was trying to do this but having problems with Kjetil Watnedal's solution because I don't use the inline linq syntax - I prefer method-style syntax. My specific problem was in trying to do dynamic sorting using a custom IComparer.
My solution ended up like this:
Given an IQueryable query like so:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
And given a run-time sort field argument:
string SortField; // Set at run-time to "Name"
The dynamic OrderBy looks like so:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
And that's using a little helper method called GetReflectedPropertyValue():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
One last thing - I mentioned that I wanted the OrderBy to use custom IComparer - because I wanted to do Natural sorting.
To do that, I just alter the OrderBy to:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
See this post for the code for NaturalSortComparer().
I've stumble this question looking for Linq multiple orderby clauses
and maybe this was what the author was looking for
Here's how to do that:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
Use dynamic linq
just add using System.Linq.Dynamic;
And use it like this to order all your columns:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
After a lot of searching this worked for me:
public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<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, property.PropertyType },
source.AsQueryable().Expression,
Expression.Quote(orderByExpression));
return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
}
First Install Dynamic
Tools --> NuGet Package Manager --> Package Manager Console
install-package System.Linq.Dynamic
Add Namespace using System.Linq.Dynamic;
Now you can use OrderBy("Name, Age DESC")
You could add it:
public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
//parse the string into property names
//Use reflection to get and sort by properties
//something like
foreach( string propname in queryString.Split(','))
input.OrderBy( x => GetPropertyValue( x, propname ) );
// I used Kjetil Watnedal's reflection example
}
The GetPropertyValue function is from Kjetil Watnedal's answer
The issue would be why? Any such sort would throw exceptions at run-time, rather than compile time (like D2VIANT's answer).
If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.
Here's something else I found interesting.
If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq
DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
orderby order.Field<DateTime>("OrderDate")
select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;
reference: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Using DataSetExtensions)
Here is one more way to do it by converting it to a DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
You can convert the IEnumerable to IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
An alternate solution uses the following class/interface. It's not truly dynamic, but it works.
public interface IID
{
int ID
{
get; set;
}
}
public static class Utils
{
public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
{
if (items.Count() == 0) return 1;
return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
}
}
Thanks to Maarten (Query a collection using PropertyInfo object in LINQ) I got this solution:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
In my case I was working on a "ColumnHeaderMouseClick" (WindowsForm) so just found the specific Column pressed and its correspondent PropertyInfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
OR
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(be sure to have your column Names matching the object Properties)
Cheers
You can use this:
public List<Book> Books(string orderField, bool desc, int skip, int take)
{
var propertyInfo = typeof(Book).GetProperty(orderField);
return _context.Books
.Where(...)
.OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
.ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
.Skip(skip)
.Take(take)
.ToList();
}
This answer is a response to the comments that need an example for the solution provided by #John Sheehan - Runscope
Please provide an example for the rest of us.
in DAL (Data Access Layer),
The IEnumerable version:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
The IQueryable version
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
// use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Now you can use the IQueryable version to bind, for example GridView in Asp.net and benefit for sorting (you can't sort using IEnumerable version)
I used Dapper as ORM and build IQueryable version and utilized sorting in GridView in asp.net so easy.
You can define a dictionary from string to Func<> like this :
Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
{
{"Rank", x => x.Rank}
};
And use it like this :
yourList.OrderBy(SortParameters["Rank"]);
In this case you can dynamically sort by string.
you can do it like this for multiple order by
IOrderedEnumerable<JToken> sort;
if (query.OrderBys[0].IsDESC)
{
sort = jarry.OrderByDescending(r => (string)r[query.OrderBys[0].Key]);
}
else
{
sort = jarry.OrderBy(r =>
(string) r[query.OrderBys[0].Key]);
}
foreach (var item in query.OrderBys.Skip(1))
{
if (item.IsDESC)
{
sort = sort.ThenByDescending(r => (string)r[item.Key]);
}
else
{
sort = sort.ThenBy(r => (string)r[item.Key]);
}
}
Convert List to IEnumerable or Iquerable, add using System.LINQ.Dynamic namespace, then u can mention the property names in comma seperated string to OrderBy Method which comes by default from System.LINQ.Dynamic.
I am able to do this with the code below. No need write long and complex code.
protected void sort_array(string field_name, string asc_desc)
{
objArrayList= Sort(objArrayList, field_name, asc_desc);
}
protected List<ArrayType> Sort(List<ArrayType> input, string property, string asc_desc)
{
if (asc_desc == "ASC")
{
return input.OrderBy(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
else
{
return input.OrderByDescending(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
}
If you are using Specification (such as Ardalis Specification)
using Microsoft.EntityFrameworkCore;
namespace TestExtensions;
public static class IQueryableExtensions
{
public static void ApplyOrder<T>(ISpecificationBuilder<T> query, string propertyName, bool ascendingOrder)
{
if (ascendingOrder)
query.OrderBy(T => EF.Property<object>(T!, propertyName));
else
query.OrderByDescending(T => EF.Property<object>(T!, propertyName));
}
}
With Net6 and EF
.AsQueryable().OrderBy((ColumnOrder.Column, ColumnOrder.Dir));
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.