Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
I found an example in the VS2008 Examples for Dynamic LINQ that allows you t开发者_运维百科o 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.
精彩评论