C# IEnumerable<T> / IQueryable<T> 上的动态 LINQ OrderBy
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/41244/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
提问by John Sheehan
I found an example in the VS2008 Examplesfor 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>
?
我在VS2008动态 LINQ示例中找到了一个示例,该示例允许您使用类似 sql 的字符串(例如OrderBy("Name, Age DESC"))
用于排序。不幸的是,所包含的方法仅适用于IQueryable<T>
。有没有办法获得此功能IEnumerable<T>
?
采纳答案by Marc Gravell
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.
要在没有动态 LINQ 库的情况下执行此操作,您只需要如下代码。这涵盖了最常见的场景,包括嵌套属性。
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.
为了让它与IEnumerable<T>
你一起工作,你可以添加一些通过的包装方法AsQueryable
- 但下面的代码是Expression
所需的核心逻辑。
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:
编辑:如果你想将它与它混合会变得更有趣dynamic
- 尽管请注意dynamic
仅适用于 LINQ-to-Objects(ORM 等的表达式树不能真正代表dynamic
查询 -MemberExpression
不支持它)。但这里有一种方法可以使用 LINQ-to-Objects 来做到这一点。请注意,选择Hashtable
是由于有利的锁定语义:
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);
}
}
}
回答by Kjetil Watnedal
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.
请注意,使用反射比直接访问属性要慢得多,因此必须调查性能。
回答by Keith
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
该GetPropertyValue
功能来自Kjetil Watnedal 的回答
The issue would be why? Any such sort would throw exceptions at run-time, rather than compile time (like D2VIANT's answer).
问题是为什么?任何此类都会在运行时抛出异常,而不是编译时(如 D2VIANT 的回答)。
If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.
如果您正在处理 Linq to Sql 并且 orderby 是一个表达式树,它将被转换为 SQL 以执行。
回答by John Sheehan
I found the answer. I can use the .AsQueryable<>()
extension method to convert my list to IQueryable, then run the dynamic order by against it.
我找到了答案。我可以使用.AsQueryable<>()
扩展方法将我的列表转换为 IQueryable,然后针对它运行动态顺序。
回答by InfoStatus
I've stumble this question looking for Linq multiple orderby clauses and maybe this was what the author was looking for
我在寻找 Linq 多个 orderby 子句时偶然发现了这个问题,也许这就是作者正在寻找的
Here's how to do that:
以下是如何做到这一点:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
回答by Mike Christiansen
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;
}
}
回答by vdhant
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;
}
回答by James McCormack
I was trying to do this but having problems with Kjetil Watnedal's solutionbecause 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
.
我试图这样做,但在使用Kjetil Watnedal 的解决方案时遇到了问题,因为我不使用内联 linq 语法——我更喜欢方法风格的语法。我的具体问题是尝试使用自定义IComparer
.
My solution ended up like this:
我的解决方案最终是这样的:
Given an IQueryable query like so:
给定一个 IQueryable 查询,如下所示:
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:
动态 OrderBy 看起来像这样:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
And that's using a little helper method called GetReflectedPropertyValue():
这使用了一个名为 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.
最后一件事 - 我提到我想要OrderBy
使用 custom IComparer
- 因为我想做自然排序。
To do that, I just alter the OrderBy
to:
为此,我只需将其更改OrderBy
为:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
See this postfor the code for NaturalSortComparer()
.
请参阅此帖子以获取NaturalSortComparer()
.
回答by Sameer Alibhai
Here's something else I found interesting. If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq
这是我发现有趣的其他事情。如果您的源是数据表,则可以使用动态排序,而无需使用 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)
参考:http: //msdn.microsoft.com/en-us/library/bb669083.aspx(使用 DataSetExtensions)
Here is one more way to do it by converting it to a DataView:
这是通过将其转换为 DataView 的另一种方法:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
回答by Adam Anderson
Just stumbled across this question.
刚刚偶然发现了这个问题。
Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:
使用上面的 Marc 的 ApplyOrder 实现,我组合了一个扩展方法来处理类似 SQL 的字符串,例如:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
详细信息可以在这里找到:http: //aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html